Skip to content

Commit 743bf28

Browse files
committed
[lldb][Mach-O] Don't read symbol table of specially marked binary (llvm#129967)
We have a binary image on Darwin that has no code, only metadata. It has a large symbol table with many external symbol names that will not be needed in the debugger. And it is possible to not have this binary on the debugger system - so lldb must read all of the symbol names out of memory, one at a time, which can be quite slow. We're adding a section __TEXT,__lldb_no_nlist, to this binary to indicate that lldb should not read the nlist symbols for it when we are reading out of memory. If lldb is run with an on-disk version of the binary, we will load the symbol table as we normally would, there's no benefit to handling this binary differently. I added a test where I create a dylib with this specially named section, launch the process. The main binary deletes the dylib from the disk so lldb is forced to read it out of memory. lldb attaches to the binary, confirms that the dylib is present in the process and is a memory Module. If the binary is not present, or lldb found the on-disk copy because it hasn't been deleted yet, we delete the target, flush the Debugger's module cache, sleep and retry, up to ten times. I create the specially named section by compiling an assembly file that puts a byte in the section which makes for a bit of a messy Makefile (the pre-canned actions to build a dylib don't quite handle this case) but I don't think it's much of a problem. This is a purely skipUnlessDarwin test case. Relanding this change with a restructured Makefiles for the test case that should pass on the CI bots. rdar://146167816 (cherry picked from commit 1a31bb3)
1 parent b3bb7aa commit 743bf28

File tree

9 files changed

+208
-24
lines changed

9 files changed

+208
-24
lines changed

lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,11 @@ ConstString ObjectFileMachO::GetSectionNameEHFrame() {
921921
return g_section_name_eh_frame;
922922
}
923923

924+
ConstString ObjectFileMachO::GetSectionNameLLDBNoNlist() {
925+
static ConstString g_section_name_lldb_no_nlist("__lldb_no_nlist");
926+
return g_section_name_lldb_no_nlist;
927+
}
928+
924929
bool ObjectFileMachO::MagicBytesMatch(DataBufferSP data_sp,
925930
lldb::addr_t data_offset,
926931
lldb::addr_t data_length) {
@@ -2395,15 +2400,54 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) {
23952400
uint32_t memory_module_load_level = eMemoryModuleLoadLevelComplete;
23962401
bool is_shared_cache_image = IsSharedCacheBinary();
23972402
bool is_local_shared_cache_image = is_shared_cache_image && !IsInMemory();
2403+
2404+
ConstString g_segment_name_TEXT = GetSegmentNameTEXT();
2405+
ConstString g_segment_name_DATA = GetSegmentNameDATA();
2406+
ConstString g_segment_name_DATA_DIRTY = GetSegmentNameDATA_DIRTY();
2407+
ConstString g_segment_name_DATA_CONST = GetSegmentNameDATA_CONST();
2408+
ConstString g_segment_name_OBJC = GetSegmentNameOBJC();
2409+
ConstString g_section_name_eh_frame = GetSectionNameEHFrame();
2410+
ConstString g_section_name_lldb_no_nlist = GetSectionNameLLDBNoNlist();
2411+
SectionSP text_section_sp(
2412+
section_list->FindSectionByName(g_segment_name_TEXT));
2413+
SectionSP data_section_sp(
2414+
section_list->FindSectionByName(g_segment_name_DATA));
23982415
SectionSP linkedit_section_sp(
23992416
section_list->FindSectionByName(GetSegmentNameLINKEDIT()));
2417+
SectionSP data_dirty_section_sp(
2418+
section_list->FindSectionByName(g_segment_name_DATA_DIRTY));
2419+
SectionSP data_const_section_sp(
2420+
section_list->FindSectionByName(g_segment_name_DATA_CONST));
2421+
SectionSP objc_section_sp(
2422+
section_list->FindSectionByName(g_segment_name_OBJC));
2423+
SectionSP eh_frame_section_sp;
2424+
SectionSP lldb_no_nlist_section_sp;
2425+
if (text_section_sp.get()) {
2426+
eh_frame_section_sp = text_section_sp->GetChildren().FindSectionByName(
2427+
g_section_name_eh_frame);
2428+
lldb_no_nlist_section_sp = text_section_sp->GetChildren().FindSectionByName(
2429+
g_section_name_lldb_no_nlist);
2430+
} else {
2431+
eh_frame_section_sp =
2432+
section_list->FindSectionByName(g_section_name_eh_frame);
2433+
lldb_no_nlist_section_sp =
2434+
section_list->FindSectionByName(g_section_name_lldb_no_nlist);
2435+
}
24002436

24012437
if (process && m_header.filetype != llvm::MachO::MH_OBJECT &&
24022438
!is_local_shared_cache_image) {
24032439
Target &target = process->GetTarget();
24042440

24052441
memory_module_load_level = target.GetMemoryModuleLoadLevel();
24062442

2443+
// If __TEXT,__lldb_no_nlist section is present in this binary,
2444+
// and we're reading it out of memory, do not read any of the
2445+
// nlist entries. They are not needed in lldb and it may be
2446+
// expensive to load these. This is to handle a dylib consisting
2447+
// of only metadata, no code, but it has many nlist entries.
2448+
if (lldb_no_nlist_section_sp)
2449+
memory_module_load_level = eMemoryModuleLoadLevelMinimal;
2450+
24072451
// Reading mach file from memory in a process or core file...
24082452

24092453
if (linkedit_section_sp) {
@@ -2528,30 +2572,6 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) {
25282572

25292573
const bool have_strtab_data = strtab_data.GetByteSize() > 0;
25302574

2531-
ConstString g_segment_name_TEXT = GetSegmentNameTEXT();
2532-
ConstString g_segment_name_DATA = GetSegmentNameDATA();
2533-
ConstString g_segment_name_DATA_DIRTY = GetSegmentNameDATA_DIRTY();
2534-
ConstString g_segment_name_DATA_CONST = GetSegmentNameDATA_CONST();
2535-
ConstString g_segment_name_OBJC = GetSegmentNameOBJC();
2536-
ConstString g_section_name_eh_frame = GetSectionNameEHFrame();
2537-
SectionSP text_section_sp(
2538-
section_list->FindSectionByName(g_segment_name_TEXT));
2539-
SectionSP data_section_sp(
2540-
section_list->FindSectionByName(g_segment_name_DATA));
2541-
SectionSP data_dirty_section_sp(
2542-
section_list->FindSectionByName(g_segment_name_DATA_DIRTY));
2543-
SectionSP data_const_section_sp(
2544-
section_list->FindSectionByName(g_segment_name_DATA_CONST));
2545-
SectionSP objc_section_sp(
2546-
section_list->FindSectionByName(g_segment_name_OBJC));
2547-
SectionSP eh_frame_section_sp;
2548-
if (text_section_sp.get())
2549-
eh_frame_section_sp = text_section_sp->GetChildren().FindSectionByName(
2550-
g_section_name_eh_frame);
2551-
else
2552-
eh_frame_section_sp =
2553-
section_list->FindSectionByName(g_section_name_eh_frame);
2554-
25552575
const bool is_arm = (m_header.cputype == llvm::MachO::CPU_TYPE_ARM);
25562576
const bool always_thumb = GetArchitecture().IsAlwaysThumbInstructions();
25572577

lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ class ObjectFileMachO : public lldb_private::ObjectFile {
285285
static lldb_private::ConstString GetSegmentNameDWARF();
286286
static lldb_private::ConstString GetSegmentNameLLVM_COV();
287287
static lldb_private::ConstString GetSectionNameEHFrame();
288+
static lldb_private::ConstString GetSectionNameLLDBNoNlist();
288289

289290
llvm::MachO::dysymtab_command m_dysymtab;
290291
std::vector<llvm::MachO::section_64> m_mach_sections;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
C_SOURCES := main.c
2+
LD_EXTRAS = -Wl,-rpath "-Wl,$(shell pwd)" -L. -lno-nlists -lhas-nlists
3+
4+
include Makefile.rules
5+
6+
a.out: dylib_HasNlists dylib_NoNlists
7+
8+
dylib_HasNlists:
9+
"$(MAKE)" -f $(MAKEFILE_RULES) \
10+
DYLIB_ONLY=YES DYLIB_NAME=has-nlists DYLIB_C_SOURCES=has-nlists.c
11+
12+
dylib_NoNlists:
13+
"$(MAKE)" VPATH=$(SRCDIR) -I $(SRCDIR) -f $(SRCDIR)/NoNlists.mk
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
DYLIB_ONLY := YES
2+
DYLIB_NAME := no-nlists
3+
DYLIB_C_SOURCES := no-nlists.c
4+
DYLIB_OBJECTS += no-nlist-sect.o
5+
6+
no-nlist-sect.o:
7+
$(CC) $(CFLAGS) -c -o no-nlist-sect.o $(SRCDIR)/no-nlist-sect.s
8+
9+
include Makefile.rules
10+
11+
clean::
12+
rm -rf *.o *.dylib a.out *.dSYM
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""
2+
Test that we read don't read the nlist symbols for a specially marked dylib
3+
when read from memory.
4+
"""
5+
6+
import os
7+
import lldb
8+
from lldbsuite.test.decorators import *
9+
from lldbsuite.test.lldbtest import *
10+
from lldbsuite.test import lldbutil
11+
from time import sleep
12+
13+
14+
class NoNlistsTestCase(TestBase):
15+
NO_DEBUG_INFO_TESTCASE = True
16+
17+
@skipIfRemote
18+
@skipUnlessDarwin
19+
def test_no_nlist_symbols(self):
20+
self.build()
21+
22+
exe = os.path.realpath(self.getBuildArtifact("a.out"))
23+
24+
# Use a file as a synchronization point between test and inferior.
25+
pid_file_path = lldbutil.append_to_process_working_directory(
26+
self, "pid_file_%d" % (int(time.time()))
27+
)
28+
self.addTearDownHook(
29+
lambda: self.run_platform_command("rm %s" % (pid_file_path))
30+
)
31+
32+
# Spawn a new process
33+
popen = self.spawnSubprocess(exe, [pid_file_path])
34+
35+
pid = lldbutil.wait_for_file_on_target(self, pid_file_path)
36+
37+
os.unlink(self.getBuildArtifact("libno-nlists.dylib"))
38+
os.unlink(self.getBuildArtifact("libhas-nlists.dylib"))
39+
40+
self.runCmd("process attach -p " + str(pid))
41+
target = self.dbg.GetSelectedTarget()
42+
process = target.GetProcess()
43+
m_no_nlist = target.FindModule(lldb.SBFileSpec("libno-nlists.dylib"))
44+
m_has_nlist = target.FindModule(lldb.SBFileSpec("libhas-nlists.dylib"))
45+
46+
self.assertTrue(process, PROCESS_IS_VALID)
47+
48+
if self.TraceOn():
49+
self.runCmd("image list")
50+
self.runCmd("target modules dump symtab libno-nlists.dylib")
51+
self.runCmd("target modules dump symtab libhas-nlists.dylib")
52+
53+
# Test that we found libno-nlists.dylib, it is a memory
54+
# module, and that it has no symbols.
55+
self.assertTrue(m_no_nlist.IsValid())
56+
self.assertFalse(m_no_nlist.IsFileBacked())
57+
self.assertEqual(m_no_nlist.GetNumSymbols(), 0)
58+
59+
# Test that we found libhas-nlists.dylib, it is a memory
60+
# module, and that it has more than zero symbols.
61+
self.assertTrue(m_has_nlist.IsValid())
62+
self.assertFalse(m_has_nlist.IsFileBacked())
63+
self.assertGreater(m_has_nlist.GetNumSymbols(), 0)
64+
65+
# And as a sanity check, get the main binary's module,
66+
# test that it is file backed and that it has more than
67+
# zero symbols.
68+
m_exe = target.FindModule(lldb.SBFileSpec("a.out"))
69+
self.assertTrue(m_exe.IsValid())
70+
self.assertTrue(m_exe.IsFileBacked())
71+
self.assertGreater(m_exe.GetNumSymbols(), 0)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int get_return_value2() { return 20; }
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include <fcntl.h>
2+
#include <libgen.h>
3+
#include <stdbool.h>
4+
#include <stdint.h>
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
#include <sys/errno.h>
9+
#include <sys/param.h>
10+
#include <sys/stat.h>
11+
#include <unistd.h>
12+
13+
int get_return_value();
14+
int get_return_value2();
15+
16+
// Create \a file_name with the c-string of our
17+
// pid in it. Initially open & write the contents
18+
// to a temporary file, then move it to the actual
19+
// filename once writing is completed.
20+
bool writePid(const char *file_name, const pid_t pid) {
21+
char *tmp_file_name = (char *)malloc(strlen(file_name) + 16);
22+
strcpy(tmp_file_name, file_name);
23+
strcat(tmp_file_name, "_tmp");
24+
int fd = open(tmp_file_name, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR);
25+
if (fd == -1) {
26+
fprintf(stderr, "open(%s) failed: %s\n", tmp_file_name, strerror(errno));
27+
free(tmp_file_name);
28+
return false;
29+
}
30+
char buffer[64];
31+
snprintf(buffer, sizeof(buffer), "%ld", (long)pid);
32+
bool res = true;
33+
if (write(fd, buffer, strlen(buffer)) == -1) {
34+
fprintf(stderr, "write(%s) failed: %s\n", buffer, strerror(errno));
35+
res = false;
36+
}
37+
close(fd);
38+
39+
if (rename(tmp_file_name, file_name) == -1) {
40+
fprintf(stderr, "rename(%s, %s) failed: %s\n", tmp_file_name, file_name,
41+
strerror(errno));
42+
res = false;
43+
}
44+
free(tmp_file_name);
45+
46+
return res;
47+
}
48+
49+
int main(int argc, char **argv) {
50+
if (writePid(argv[1], getpid())) {
51+
// we've signaled lldb we are ready to be attached to,
52+
// this sleep() call will be interrupted when lldb
53+
// attaches.
54+
sleep(200);
55+
} else {
56+
printf("Error writing pid to '%s', exiting.\n", argv[1]);
57+
exit(3);
58+
}
59+
60+
int retval = get_return_value();
61+
return retval + get_return_value2();
62+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.section __TEXT,__lldb_no_nlist,regular,pure_instructions
2+
.p2align 2
3+
.byte 0x10
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int get_return_value() { return 10; }

0 commit comments

Comments
 (0)