Index: util/mac/mach_o_image_reader_test.cc |
diff --git a/util/mac/mach_o_image_reader_test.cc b/util/mac/mach_o_image_reader_test.cc |
index fbc7b50b4c46c4f251bdc3af9b792aed3400013c..d9c6e12223c6c9720624489af73eb9844fb73aab 100644 |
--- a/util/mac/mach_o_image_reader_test.cc |
+++ b/util/mac/mach_o_image_reader_test.cc |
@@ -15,9 +15,12 @@ |
#include "util/mac/mach_o_image_reader.h" |
#include <dlfcn.h> |
+#include <mach-o/dyld.h> |
#include <mach-o/dyld_images.h> |
#include <mach-o/getsect.h> |
+#include <mach-o/ldsyms.h> |
#include <mach-o/loader.h> |
+#include <mach-o/nlist.h> |
#include <stdint.h> |
#include "base/strings/stringprintf.h" |
@@ -29,6 +32,9 @@ |
#include "util/misc/uuid.h" |
#include "util/test/mac/dyld.h" |
+// This file is responsible for testing MachOImageReader, |
+// MachOImageSegmentReader, and MachOImageSymbolTableReader. |
+ |
namespace { |
using namespace crashpad; |
@@ -41,12 +47,17 @@ const uint32_t kMachMagic = MH_MAGIC_64; |
typedef segment_command_64 SegmentCommand; |
const uint32_t kSegmentCommand = LC_SEGMENT_64; |
typedef section_64 Section; |
+typedef nlist_64 Nlist; |
#else |
typedef mach_header MachHeader; |
const uint32_t kMachMagic = MH_MAGIC; |
typedef segment_command SegmentCommand; |
const uint32_t kSegmentCommand = LC_SEGMENT; |
typedef section Section; |
+ |
+// This needs to be called “struct nlist” because “nlist” without the struct |
+// refers to the nlist() function. |
+typedef struct nlist Nlist; |
#endif |
#if defined(ARCH_CPU_X86_64) |
@@ -188,11 +199,14 @@ void ExpectSegmentCommand(const SegmentCommand* expect_segment, |
} |
// Test the parent MachOImageReader’s GetSectionAtIndex as well. |
+ const MachOImageSegmentReader* containing_segment; |
mach_vm_address_t actual_section_address_at_index; |
const process_types::section* actual_section_from_image_at_index = |
actual_image->GetSectionAtIndex(++(*section_index), |
+ &containing_segment, |
&actual_section_address_at_index); |
EXPECT_EQ(actual_section, actual_section_from_image_at_index); |
+ EXPECT_EQ(actual_segment, containing_segment); |
EXPECT_EQ(actual_section_address, actual_section_address_at_index); |
} |
@@ -225,6 +239,7 @@ void ExpectSegmentCommands(const MachHeader* expect_image, |
reinterpret_cast<const load_command*>(&commands_base[position]); |
ASSERT_LE(position + command->cmdsize, expect_image->sizeofcmds); |
if (command->cmd == kSegmentCommand) { |
+ ASSERT_GE(command->cmdsize, sizeof(SegmentCommand)); |
const SegmentCommand* expect_segment = |
reinterpret_cast<const SegmentCommand*>(command); |
std::string segment_name = |
@@ -252,8 +267,9 @@ void ExpectSegmentCommands(const MachHeader* expect_image, |
if (test_section_index_bounds) { |
// GetSectionAtIndex uses a 1-based index. Make sure that the range is |
// correct. |
- EXPECT_EQ(NULL, actual_image->GetSectionAtIndex(0, NULL)); |
- EXPECT_EQ(NULL, actual_image->GetSectionAtIndex(section_index + 1, NULL)); |
+ EXPECT_EQ(NULL, actual_image->GetSectionAtIndex(0, NULL, NULL)); |
+ EXPECT_EQ(NULL, |
+ actual_image->GetSectionAtIndex(section_index + 1, NULL, NULL)); |
} |
// Make sure that by-name lookups for names that don’t exist work properly: |
@@ -280,16 +296,24 @@ void ExpectSegmentCommands(const MachHeader* expect_image, |
EXPECT_FALSE(actual_image->GetSectionByName(SEG_LINKEDIT, SECT_TEXT, NULL)); |
} |
+// In some cases, the expected slide value for an image is unknown, because no |
+// reasonable API to return it is provided. When this happens, use kSlideUnknown |
+// to avoid checking the actual slide value against anything. |
+const mach_vm_size_t kSlideUnknown = std::numeric_limits<mach_vm_size_t>::max(); |
+ |
// Verifies that |expect_image| is a vaild Mach-O header for the current system |
// by checking its |magic| and |cputype| fields. Then, verifies that the |
// information in |actual_image| matches that in |expect_image|. The |filetype| |
-// field is examined, and actual_image->Address() is compared to |
-// |expect_image_address|. Various other attributes of |actual_image| are |
-// sanity-checked depending on the Mach-O file type. Finally, |
-// ExpectSegmentCommands() is called to verify all that all of the segments |
-// match; |test_section_index_bounds| is used as an argument to that function. |
+// field is examined, actual_image->Address() is compared to |
+// |expect_image_address|, and actual_image->Slide() is compared to |
+// |expect_image_slide|, unless |expect_image_slide| is kSlideUnknown. Various |
+// other attributes of |actual_image| are sanity-checked depending on the Mach-O |
+// file type. Finally, ExpectSegmentCommands() is called to verify all that all |
+// of the segments match; |test_section_index_bounds| is used as an argument to |
+// that function. |
void ExpectMachImage(const MachHeader* expect_image, |
mach_vm_address_t expect_image_address, |
+ mach_vm_size_t expect_image_slide, |
const MachOImageReader* actual_image, |
bool test_section_index_bounds) { |
ASSERT_TRUE(expect_image); |
@@ -300,6 +324,9 @@ void ExpectMachImage(const MachHeader* expect_image, |
EXPECT_EQ(expect_image->filetype, actual_image->FileType()); |
EXPECT_EQ(expect_image_address, actual_image->Address()); |
+ if (expect_image_slide != kSlideUnknown) { |
+ EXPECT_EQ(expect_image_slide, actual_image->Slide()); |
+ } |
mach_vm_address_t actual_text_segment_address; |
mach_vm_size_t actual_text_segment_size; |
@@ -329,6 +356,129 @@ void ExpectMachImage(const MachHeader* expect_image, |
actual_image->UUID(&uuid); |
ExpectSegmentCommands(expect_image, actual_image, test_section_index_bounds); |
+ if (testing::Test::HasFatalFailure()) { |
+ return; |
+ } |
+} |
+ |
+// Verifies the symbol whose Nlist structure is |entry| and whose name is |name| |
+// matches the value of a symbol by the same name looked up in |actual_image|. |
+// MachOImageReader::LookUpExternalDefinedSymbol() is used for this purpose. |
+// Only external defined symbols are considered, other types of symbols are |
+// excluded because LookUpExternalDefinedSymbol() only deals with external |
+// defined symbols. |
+void ExpectSymbol(const Nlist* entry, |
+ const char* name, |
+ const MachOImageReader* actual_image) { |
+ SCOPED_TRACE(name); |
+ |
+ uint32_t entry_type = entry->n_type & N_TYPE; |
+ if ((entry->n_type & N_STAB) == 0 && (entry->n_type & N_PEXT) == 0 && |
+ entry_type != N_UNDF && entry_type != N_PBUD && |
+ (entry->n_type & N_EXT) == 1) { |
+ |
+ // Note that this catches more symbols than MachOImageSymbolTableReader |
+ // does. This test looks for all external defined symbols, but the |
+ // implementation excludes indirect (N_INDR) symbols. This is intentional, |
+ // because indirect symbols are currently not seen in the wild, but if they |
+ // begin to be used more widely, this test is expected to catch them so that |
+ // a decision can be made regarding whether support ought to be implemented. |
+ mach_vm_address_t actual_address; |
+ ASSERT_TRUE( |
+ actual_image->LookUpExternalDefinedSymbol(name, &actual_address)); |
+ |
+ // Since the nlist interface was used to read the symbol, use it to compute |
+ // the symbol address too. This isn’t perfect, and it should be possible in |
+ // theory to use dlsym() to get the expected address of a symbol. In |
+ // practice, dlsym() is difficult to use when only a MachHeader* is |
+ // available as in this function, as opposed to a void* opaque handle. It is |
+ // possible to get a void* handle by using dladdr() to find the file name |
+ // corresponding to the MachHeader*, and using dlopen() again on that name, |
+ // assuming it hasn’t changed on disk since being loaded. However, even with |
+ // that being done, dlsym() can only deal with symbols whose names begin |
+ // with an underscore (and requires that the leading underscore be trimmed). |
+ // dlsym() will also return different addresses for symbols that are |
+ // resolved via symbol resolver. |
+ mach_vm_address_t expect_address = entry->n_value; |
+ if (entry_type == N_SECT) { |
+ EXPECT_GE(entry->n_sect, 1u); |
+ expect_address += actual_image->Slide(); |
+ } else { |
+ EXPECT_EQ(NO_SECT, entry->n_sect); |
+ } |
+ |
+ EXPECT_EQ(expect_address, actual_address); |
+ } |
+ |
+ // You’d think that it might be a good idea to verify that if the conditions |
+ // above weren’t met, that the symbol didn’t show up in actual_image’s symbol |
+ // table at all. Unfortunately, it’s possible for the same name to show up as |
+ // both an external defined symbol and as something else, so it’s not possible |
+ // to verify this reliably. |
+} |
+ |
+// Locates the symbol table in |expect_image| and verifies that all of the |
+// external defined symbols found there are also present and have the same |
+// values in |actual_image|. ExpectSymbol() is used to verify the actual symbol. |
+void ExpectSymbolTable(const MachHeader* expect_image, |
+ const MachOImageReader* actual_image) { |
+ // This intentionally consults only LC_SYMTAB and not LC_DYSYMTAB so that it |
+ // can look at the larger set of all symbols. The actual implementation being |
+ // tested is free to consult LC_DYSYMTAB, but that’s considered an |
+ // optimization. It’s not necessary for the test, and it’s better for the test |
+ // to expose bugs in that optimization rather than duplicate them. |
+ const char* commands_base = reinterpret_cast<const char*>(&expect_image[1]); |
Robert Sesek
2014/09/05 19:04:16
Why [1]?
Mark Mentovai
2014/09/05 20:22:31
rsesek wrote:
Robert Sesek
2014/09/05 20:41:31
I'd just add a comment saying it's moving past the
|
+ uint32_t position = 0; |
+ const symtab_command* symtab = NULL; |
+ const SegmentCommand* linkedit = NULL; |
+ for (uint32_t index = 0; index < expect_image->ncmds; ++index) { |
+ ASSERT_LT(position, expect_image->sizeofcmds); |
+ const load_command* command = |
+ reinterpret_cast<const load_command*>(&commands_base[position]); |
+ ASSERT_LE(position + command->cmdsize, expect_image->sizeofcmds); |
+ if (command->cmd == LC_SYMTAB) { |
+ ASSERT_FALSE(symtab); |
+ ASSERT_EQ(sizeof(symtab_command), command->cmdsize); |
+ symtab = reinterpret_cast<const symtab_command*>(command); |
+ } else if (command->cmd == kSegmentCommand) { |
+ ASSERT_GE(command->cmdsize, sizeof(SegmentCommand)); |
+ const SegmentCommand* segment = |
+ reinterpret_cast<const SegmentCommand*>(command); |
+ std::string segment_name = |
+ MachOImageSegmentReader::SegmentNameString(segment->segname); |
+ if (segment_name == SEG_LINKEDIT) { |
+ ASSERT_FALSE(linkedit); |
+ linkedit = segment; |
+ } |
+ } |
+ position += command->cmdsize; |
+ } |
+ |
+ if (symtab) { |
+ ASSERT_TRUE(linkedit); |
+ |
+ const char* linkedit_base = |
+ reinterpret_cast<const char*>(linkedit->vmaddr + actual_image->Slide()); |
+ const Nlist* nlist = reinterpret_cast<const Nlist*>( |
+ linkedit_base + symtab->symoff - linkedit->fileoff); |
+ const char* strtab = linkedit_base + symtab->stroff - linkedit->fileoff; |
+ |
+ for (uint32_t index = 0; index < symtab->nsyms; ++index) { |
+ const Nlist* entry = nlist + index; |
+ const char* name = strtab + entry->n_un.n_strx; |
+ ExpectSymbol(entry, name, actual_image); |
+ if (testing::Test::HasFatalFailure()) { |
+ return; |
+ } |
+ } |
+ } |
+ |
+ mach_vm_address_t ignore; |
+ EXPECT_FALSE(actual_image->LookUpExternalDefinedSymbol("", &ignore)); |
+ EXPECT_FALSE( |
+ actual_image->LookUpExternalDefinedSymbol("NoSuchSymbolName", &ignore)); |
+ EXPECT_FALSE( |
+ actual_image->LookUpExternalDefinedSymbol("_NoSuchSymbolName", &ignore)); |
} |
TEST(MachOImageReader, Self_MainExecutable) { |
@@ -336,46 +486,64 @@ TEST(MachOImageReader, Self_MainExecutable) { |
ASSERT_TRUE(process_reader.Initialize(mach_task_self())); |
const MachHeader* mh_execute_header = reinterpret_cast<MachHeader*>( |
- dlsym(RTLD_MAIN_ONLY, "_mh_execute_header")); |
+ dlsym(RTLD_MAIN_ONLY, MH_EXECUTE_SYM)); |
ASSERT_NE(static_cast<void*>(NULL), mh_execute_header); |
mach_vm_address_t mh_execute_header_address = |
reinterpret_cast<mach_vm_address_t>(mh_execute_header); |
MachOImageReader image_reader; |
ASSERT_TRUE(image_reader.Initialize( |
- &process_reader, mh_execute_header_address, "mh_execute_header")); |
+ &process_reader, mh_execute_header_address, "executable")); |
EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), image_reader.FileType()); |
- ExpectMachImage( |
- mh_execute_header, mh_execute_header_address, &image_reader, true); |
+ // The main executable has image index 0. |
+ intptr_t image_slide = _dyld_get_image_vmaddr_slide(0); |
+ |
+ ExpectMachImage(mh_execute_header, |
+ mh_execute_header_address, |
+ image_slide, |
+ &image_reader, |
+ true); |
+ if (Test::HasFatalFailure()) { |
+ return; |
+ } |
+ |
+ // This symbol, __mh_execute_header, is known to exist in all MH_EXECUTE |
+ // Mach-O files. |
+ mach_vm_address_t symbol_address; |
+ ASSERT_TRUE(image_reader.LookUpExternalDefinedSymbol(_MH_EXECUTE_SYM, |
+ &symbol_address)); |
+ EXPECT_EQ(mh_execute_header_address, symbol_address); |
+ |
+ ExpectSymbolTable(mh_execute_header, &image_reader); |
+ if (Test::HasFatalFailure()) { |
+ return; |
+ } |
} |
TEST(MachOImageReader, Self_DyldImages) { |
ProcessReader process_reader; |
ASSERT_TRUE(process_reader.Initialize(mach_task_self())); |
- const struct dyld_all_image_infos* dyld_image_infos = |
- _dyld_get_all_image_infos(); |
- ASSERT_GE(dyld_image_infos->version, 1u); |
- ASSERT_TRUE(dyld_image_infos->infoArray); |
+ uint32_t count = _dyld_image_count(); |
+ ASSERT_GE(count, 1u); |
- for (uint32_t index = 0; index < dyld_image_infos->infoArrayCount; ++index) { |
- const dyld_image_info* dyld_image = &dyld_image_infos->infoArray[index]; |
- SCOPED_TRACE(base::StringPrintf( |
- "index %u, image %s", index, dyld_image->imageFilePath)); |
+ for (uint32_t index = 0; index < count; ++index) { |
+ const char* image_name = _dyld_get_image_name(index); |
+ SCOPED_TRACE(base::StringPrintf("index %u, image %s", index, image_name)); |
- // dyld_image_info::imageLoadAddress is poorly-declared: it’s declared as |
+ // _dyld_get_image_header() is poorly-declared: it’s declared as returning |
// const mach_header* in both 32-bit and 64-bit environments, but in the |
// 64-bit environment, it should be const mach_header_64*. |
const MachHeader* mach_header = |
- reinterpret_cast<const MachHeader*>(dyld_image->imageLoadAddress); |
+ reinterpret_cast<const MachHeader*>(_dyld_get_image_header(index)); |
mach_vm_address_t image_address = |
reinterpret_cast<mach_vm_address_t>(mach_header); |
MachOImageReader image_reader; |
ASSERT_TRUE(image_reader.Initialize( |
- &process_reader, image_address, dyld_image->imageFilePath)); |
+ &process_reader, image_address, image_name)); |
uint32_t file_type = image_reader.FileType(); |
if (index == 0) { |
@@ -384,7 +552,14 @@ TEST(MachOImageReader, Self_DyldImages) { |
EXPECT_TRUE(file_type == MH_DYLIB || file_type == MH_BUNDLE); |
} |
- ExpectMachImage(mach_header, image_address, &image_reader, false); |
+ intptr_t image_slide = _dyld_get_image_vmaddr_slide(index); |
+ ExpectMachImage( |
+ mach_header, image_address, image_slide, &image_reader, false); |
+ if (Test::HasFatalFailure()) { |
+ return; |
+ } |
+ |
+ ExpectSymbolTable(mach_header, &image_reader); |
if (Test::HasFatalFailure()) { |
return; |
} |
@@ -392,6 +567,11 @@ TEST(MachOImageReader, Self_DyldImages) { |
// Now that all of the modules have been verified, make sure that dyld itself |
// can be read properly too. |
+ const struct dyld_all_image_infos* dyld_image_infos = |
+ _dyld_get_all_image_infos(); |
+ ASSERT_GE(dyld_image_infos->version, 1u); |
+ EXPECT_EQ(count, dyld_image_infos->infoArrayCount); |
+ |
if (dyld_image_infos->version >= 2) { |
SCOPED_TRACE("dyld"); |
@@ -407,7 +587,14 @@ TEST(MachOImageReader, Self_DyldImages) { |
EXPECT_EQ(static_cast<uint32_t>(MH_DYLINKER), image_reader.FileType()); |
- ExpectMachImage(mach_header, image_address, &image_reader, false); |
+ // There’s no good API to get dyld’s slide, so don’t bother checking it. |
+ ExpectMachImage( |
+ mach_header, image_address, kSlideUnknown, &image_reader, false); |
+ if (Test::HasFatalFailure()) { |
+ return; |
+ } |
+ |
+ ExpectSymbolTable(mach_header, &image_reader); |
if (Test::HasFatalFailure()) { |
return; |
} |
@@ -434,7 +621,11 @@ TEST(MachOImageReader, Self_DyldImages) { |
ASSERT_TRUE( |
image_reader.Initialize(&process_reader, image_address, "uuid")); |
- ExpectMachImage(mach_header, image_address, &image_reader, false); |
+ // There’s no good way to get the image’s slide here, although the image |
+ // should have already been checked along with its slide above, in the |
+ // loop through all images. |
+ ExpectMachImage( |
+ mach_header, image_address, kSlideUnknown, &image_reader, false); |
UUID expected_uuid; |
expected_uuid.InitializeFromBytes(dyld_image->imageUUID); |