Skip to content

Commit a1f545e

Browse files
authored
jGetLoadedDynamicLibrariesInfos can inspect machos not yet loaded (#5065)
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 (cherry picked from commit ac49e90)
1 parent d850888 commit a1f545e

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
@@ -72,9 +72,11 @@ class MachProcess {
7272
uint64_t load_address;
7373
uint64_t mod_date; // may not be available - 0 if so
7474
struct mach_o_information macho_info;
75+
bool is_valid_mach_header;
7576

7677
binary_image_information()
77-
: filename(), load_address(INVALID_NUB_ADDRESS), mod_date(0) {}
78+
: filename(), load_address(INVALID_NUB_ADDRESS), mod_date(0),
79+
is_valid_mach_header(false) {}
7880
};
7981

8082
// Child process control

0 commit comments

Comments
 (0)