Skip to content

Commit ac49e90

Browse files
committed
jGetLoadedDynamicLibrariesInfos can inspect machos not yet loaded
jGetLoadedDynamicLibrariesInfos normally checks with dyld to find the list of binaries loaded in the inferior, and getting the filepath, before trying to parse the Mach-O binary in inferior memory. This allows for debugserver to parse a Mach-O binary present in memory, but not yet registered with dyld. This patch also adds some simple sanity checks that we're reading a Mach-O header before we begin stepping through load commands, because we won't have the sanity check of consulting dyld for the list of loaded binaries before parsing. Also adds a testcase. [This patch was reverted after causing a testsuite failure on a CI bot; I haven't been able to repro the failure outside the CI, but I have a theory that my sanity check on cputype which only matched arm64 and x86_64 - and the CI machine may have a watch simulator that is still using i386.] Differential Revision: https://reviews.llvm.org/D128956 rdar://95737734
1 parent c66303c commit ac49e90

File tree

5 files changed

+234
-84
lines changed

5 files changed

+234
-84
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES = main.c
2+
3+
include Makefile.rules
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Test that debugserver will parse a mach-o in inferior memory even if it's not loaded."""
2+
3+
import os
4+
import re
5+
import subprocess
6+
7+
import lldb
8+
from lldbsuite.test.decorators import *
9+
from lldbsuite.test.lldbtest import *
10+
from lldbsuite.test import lldbutil
11+
12+
class TestUnregisteredMacho(TestBase):
13+
14+
# newer debugserver required for jGetLoadedDynamicLibrariesInfos
15+
# to support this
16+
@skipIfOutOfTreeDebugserver
17+
@no_debug_info_test
18+
@skipUnlessDarwin
19+
def test(self):
20+
self.build()
21+
target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
22+
self, "// break here", lldb.SBFileSpec("main.c"))
23+
24+
frame = thread.GetFrameAtIndex(0)
25+
macho_buf = frame.GetValueForVariablePath("macho_buf")
26+
macho_addr = macho_buf.GetValueAsUnsigned()
27+
invalid_macho_addr = macho_buf.GetValueAsUnsigned() + 4
28+
gdb_packet = "process plugin packet send 'jGetLoadedDynamicLibrariesInfos:{\"solib_addresses\":[%d]}]'" % macho_addr
29+
30+
# Send the jGetLoadedDynamicLibrariesInfos packet
31+
# to debugserver, asking it to parse the mach-o binary
32+
# at this address and give us the UUID etc, even though
33+
# dyld doesn't think there is a binary at that address.
34+
# We won't get a pathname for the binary (from dyld), but
35+
# we will get to the LC_UUID and include that.
36+
self.expect (gdb_packet, substrs=['"pathname":""', '"uuid":"1B4E28BA-2FA1-11D2-883F-B9A761BDE3FB"'])
37+
38+
no_macho_gdb_packet = "process plugin packet send 'jGetLoadedDynamicLibrariesInfos:{\"solib_addresses\":[%d]}]'" % invalid_macho_addr
39+
self.expect (no_macho_gdb_packet, substrs=['response: {"images":[]}'])
40+
41+
# Test that we get back the information for the properly
42+
# formatted Mach-O binary in memory, but do not get an
43+
# entry for the invalid Mach-O address.
44+
both_gdb_packet = "process plugin packet send 'jGetLoadedDynamicLibrariesInfos:{\"solib_addresses\":[%d,%d]}]'" % (macho_addr, invalid_macho_addr)
45+
self.expect (both_gdb_packet, substrs=['"load_address":%d,' % macho_addr])
46+
self.expect (both_gdb_packet, substrs=['"load_address":%d,' % invalid_macho_addr], matching=False)
47+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include <mach-o/loader.h>
2+
#include <mach/machine.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
#include <uuid/uuid.h>
6+
7+
int main() {
8+
int size_of_load_cmds =
9+
sizeof(struct segment_command_64) + sizeof(struct uuid_command);
10+
uint8_t *macho_buf =
11+
(uint8_t *)malloc(sizeof(struct mach_header_64) + size_of_load_cmds);
12+
uint8_t *p = macho_buf;
13+
struct mach_header_64 mh;
14+
mh.magic = MH_MAGIC_64;
15+
mh.cputype = CPU_TYPE_ARM64;
16+
mh.cpusubtype = 0;
17+
mh.filetype = MH_EXECUTE;
18+
mh.ncmds = 2;
19+
mh.sizeofcmds = size_of_load_cmds;
20+
mh.flags = MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL | MH_PIE;
21+
22+
memcpy(p, &mh, sizeof(mh));
23+
p += sizeof(mh);
24+
25+
struct segment_command_64 seg;
26+
seg.cmd = LC_SEGMENT_64;
27+
seg.cmdsize = sizeof(seg);
28+
strcpy(seg.segname, "__TEXT");
29+
seg.vmaddr = 0x5000;
30+
seg.vmsize = 0x1000;
31+
seg.fileoff = 0;
32+
seg.filesize = 0;
33+
seg.maxprot = 0;
34+
seg.initprot = 0;
35+
seg.nsects = 0;
36+
seg.flags = 0;
37+
38+
memcpy(p, &seg, sizeof(seg));
39+
p += sizeof(seg);
40+
41+
struct uuid_command uuid;
42+
uuid.cmd = LC_UUID;
43+
uuid.cmdsize = sizeof(uuid);
44+
uuid_clear(uuid.uuid);
45+
uuid_parse("1b4e28ba-2fa1-11d2-883f-b9a761bde3fb", uuid.uuid);
46+
47+
memcpy(p, &uuid, sizeof(uuid));
48+
p += sizeof(uuid);
49+
50+
// If this needs to be debugged, the memory buffer can be written
51+
// to a file with
52+
// (lldb) mem rea -b -o /tmp/t -c `p - macho_buf` macho_buf
53+
// (lldb) platform shell otool -hlv /tmp/t
54+
// to verify that it is well formed.
55+
56+
// And inside lldb, it should be inspectable via
57+
// (lldb) script print(lldb.frame.locals["macho_buf"][0].GetValueAsUnsigned())
58+
// 105553162403968
59+
// (lldb) process plugin packet send
60+
// 'jGetLoadedDynamicLibrariesInfos:{"solib_addresses":[105553162403968]}]'
61+
62+
return 0; // break here
63+
}

lldb/tools/debugserver/source/MacOSX/MachProcess.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ class MachProcess {
7373
uint64_t load_address;
7474
uint64_t mod_date; // may not be available - 0 if so
7575
struct mach_o_information macho_info;
76+
bool is_valid_mach_header;
7677

7778
binary_image_information()
78-
: filename(), load_address(INVALID_NUB_ADDRESS), mod_date(0) {}
79+
: filename(), load_address(INVALID_NUB_ADDRESS), mod_date(0),
80+
is_valid_mach_header(false) {}
7981
};
8082

8183
// Child process control

0 commit comments

Comments
 (0)