This is a register context converter from Minidump to Linux reg context.
This knows the layout of the register context in the Minidump file
(which is the same as in Windows FYI) and as a result emits a binary data
buffer that matches the Linux register context binary layout.
This way we can reuse the existing RegisterContextLinux_x86_64 and
RegisterContextCorePOSIX_x86_64 classes.
Details
Diff Detail
Event Timeline
I tested this on the yet-to-be-submitted plugin code and it works fine on Linux (should work fine everywhere for that matter). I can do a backtrace, go up/down the stack frames and, of course, see all of the registers with register read
source/Plugins/Process/minidump/MinidumpParser.h | ||
---|---|---|
42 | Replace these two functions with llvm::ArrayRef<uint8_t> GetData() const | |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.h | ||
56 | This function does not reflect the actual memory layout of the data, and you are not using it. I think it should go away. | |
162 | This looks like a private utility function. It should not be in the header. | |
187 | Could you add // end namespace minidump comments. Without them it's hard to tell what is going on here. | |
unittests/Process/minidump/MinidumpParserTest.cpp | ||
176 | static Also i think having the register tests in this file is fine. |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp | ||
---|---|---|
47–48 | Can this be a const MinidumpContext_x86_64_Flags*? Then you wouldn't have to do the uint32_t conversion each time since you could just & the flag you're checking directly against this value. | |
49 | Can we use a sizeof() here? | |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.h | ||
38 | Should these be endian specific types like ulittle16_t? If someone writes a minidump on a little endian system and runs this code on a big endian system, it would fail to parse the minidump correctly. | |
162 | The order of parameters here is confusing. the output buffer should be adjacent to its length. So it should be: void writeRegister(ArrayRef<>, uint8_t*, size_t, const RegisterInfo*) That said, passing around pointers and lengths is a pattern that needs to die. I would much rather this be: void writeRegister(ArrayRef<uint8_t> ®_src, MutableArrayRef<uint8_t> reg_dest, const RegisterInfo ®); | |
unittests/Process/minidump/MinidumpParserTest.cpp | ||
177 | I would make the conversion a macro like this: #define REG_VAL(x) *(reinterpret_cast<uint64_t *>(x)) Then write the asserts inline. The reason for this is that when you do it this way, when a test fails the test runner will report that the failure happened in registerEqualToVal, which won't give you any clue about what test it failed in. if the assertion is kept in the body of the test, the failure will report which test it was in, so you can more easily see. |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp | ||
---|---|---|
47–48 | If it is then when I do a & the result is an enum class of type MinidumpContext_x86_64_Flags. And the compiler complains that this is not convertible to bool |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp | ||
---|---|---|
49 | sizeof(uint16_t), sizeof(uint32_t), etc ? |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp | ||
---|---|---|
49 | I think my comments got out of line and this is no longer at the position I suggested it. that said, I actually didn't notice all these constants being passed into writeRegister. I would propose changing this. Find the RegisterInfo class in lldb-private-types.h and add the following methods: llvm::ArrayRef<uint8_t> data(const uint8_t *context_base) const { return llvm::ArrayRef<uint8_t>(context_base + byte_offset, byte_size); } llvm::MutableArrayRef<uint8_t> mutable_data(uint8_t *context_base) const { return llvm::MutableArrayRef<uint8_t>(context_base + byte_offset, byte_size); } Then you can write: writeRegister(source_data, reg_info[lldb_cs_x86_64].mutable_data(result_base)); |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp | ||
---|---|---|
1 | Should match file name. | |
31 | It might be better to put this comment with the declaration in the header file, since it explains what to pass in and what it does. Comments in .cpp files should contain implementation details that the callers don't necessarily need to know. | |
47–48 | I think what Zach means is that you could locally define a uint32_t const, initialized with the value from the enum. Then each if statement could use that constant without a cast. Also, is this right? MinidumpContext_x86_x64_Flags::Control has two bits set, so the condition will be true if either of them is set. Is that the intended behavior? Or should you be ensuring that they're both set like this: const utin32_t ControlFlags = MinidumpContext_x86_64::Control; if ((*context_flags & ControlFlags) == ControlFlags) { ... } ? | |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.h | ||
1 | Please make this line match the file name. | |
unittests/Process/minidump/MinidumpParserTest.cpp | ||
177 | +1 to Zach's idea. Also, consider whether EXPECT_EQ is more appropriate than ASSERT_EQ for these. |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp | ||
---|---|---|
47–48 | Now that I think about I should check the arch flag bit at the beggining of this. |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp | ||
---|---|---|
49 | This will make the code very clean indeed, but there is a problem. From the RegisterInfo I only get the byte_offset and I don't use the byte_size because some of the registers in the Minidump context are smaller (e.g. cs is 16 bit). All of the registers in the Linux context are 64 bits in size. So that's why I basically hard-coded the size in each of the funciton calls. Now with the next revision I tried to make the code clearer. | |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.h | ||
38 | So I had the actual struct for the register context in the previous revision, but now I've removed it, because I actually don't use it. So added a comment describing it's layout. For the endianness part - because this code only permutes the bytes in the layout the Linux register context expects them, I don't think that here is the place to handle that. I was hoping that the Linux register context will handle it, but I don't think that's the case when I look at the code in RegisterContextCorePOSIX_x86_64::ReadRegister. Am I right - do the Linux context assume little endian? If so maybe I can also fix that. | |
unittests/Process/minidump/MinidumpParserTest.cpp | ||
177 | Probably EXPECT_EQ is better because if there is a mismatch in the reg values, one can see all of the values. I still don't have the sense when to use EXPECT_EQ and when ASSERT_EQ. |
ASSERT_EQ causes the test method to return immediately if the condition fails. EXPECT_EQ will continue running the same test body. I like to use ASSERT_EQ, for example, if you are checking the value of a pointer returned by a function. First you want to check if it's null, then you want to check that its value is the value you expect. So I would do
ASSERT_NE(p, nullptr); EXPECT_EQ(*p, 42);
Because if it's null, the rest of the statements in the test body are undefined.
Another example is if a method returns some value, say number of items in a list, and then you want to call another method to process that many items. Say:
int x = foo.GetItemCount(); const Item *items = foo.GetItems(); bool Success = ProcessItems(items, x);
If the number of items is not what you expect, it doesn't make sense to run the next statement. So you could write:
int x = foo.GetItemCount(); ASSERT_EQ(42, x); const Item *items = foo.GetItems(); EXPECT_TRUE(ProcessItems(items, x));
On the other hand, if each thing you are testing is independent of the previous statement, everything can be expect. For example:
EXPECT_EQ("0", Foo(0)); EXPECT_EQ("1", Foo(1)); EXPECT_EQ("2", Foo(2)); EXPECT_EQ("3", Foo(3));
This way you can see as much diagnostic information as possible in the output (for example you might be able to spot a pattern in which values are failing.
unittests/Process/minidump/MinidumpParserTest.cpp | ||
---|---|---|
177 | EXPECT_xxx will check the condition and report if it's wrong. But then the test proceeds. ASSERT_xxx will check the condition, and, if it's wrong, it will report _and_ terminate the current test. So my rule of thumb is:
|
unittests/Process/minidump/MinidumpParserTest.cpp | ||
---|---|---|
177 | Thanks :) |
source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp | ||
---|---|---|
51 | If they are just smaller (but never bigger), you can call take_front(N) on the result. So you could say: writeRegister(source_data, reg_info[lldb_cs_x86_64].mutable_data(result_base).take_front(2)); Now the line is starting to get long so might look ugly if it wraps, so it's up to you. It is somewhat safer at least since you will assert if you accidentally specify an invalid size, and you don't have to worry about getting the computation right. Perhaps you could combine this with the approach you've already taken here. Add the functions to RegisterInfo, then change getDestRegister to look like this: llvm::MutableArrayRef<uint8_t> getDestRegister(const RegisterInfo ®, uint8_t *context, uint32_t lldb_reg_num) { auto bytes = reg.mutable_data(context); switch (lldb_reg_num) { case lldb_cs_x86_64: case lldb_ds_x86_64: case lldb_es_x86_64: case lldb_fs_x86_64: case lldb_gs_x86_64: case lldb_ss_x86_64: return bytes.take_front(2); break; case lldb_rflags_x86_64: return bytes.take_front(4); break; default: return bytes.take_front(8); } then you could call writeRegister like this: writeRegister(source_data, getDestRegister(reg_info, result_base, lldb_cs_x86_64)); At this point though I'm not sure what's better. So don't consider this a requirement, up to you :) |
Replace these two functions with llvm::ArrayRef<uint8_t> GetData() const