[Lldb-commits] [lldb] [lldb][test] Add test for qSymbol handling with an ELF program (PR #200411)

David Spickett via lldb-commits lldb-commits at lists.llvm.org
Fri May 29 07:09:40 PDT 2026


https://github.com/DavidSpickett updated https://github.com/llvm/llvm-project/pull/200411

>From 4ec416ff9d7453b15ce424800731c7f0c4fa70a6 Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at arm.com>
Date: Fri, 29 May 2026 12:57:23 +0000
Subject: [PATCH 1/2] [lldb][test] Add test for qSymbol handling with an ELF
 program

qSymbol is sent to the debug server every time libraries are loaded.
It can respond to this with symbols it wants to know the value for.

It's rarely used so I expect that's why we had zero test coverage for it.

In this commit I'm adding a test case for ELF, derived from the bug
being reported and fixed in #200134.

Ideally this would run for MachO as well, as desired behaviour differs
there. However, we need to fake the debug server and wrapping a real
server is going to add too much complexity.

The mock debug server has a list of symbols to ask for and records
the results. If anything unexpected happens it'll assert with Python's
built in assert as that's all I can use in this class.

Unfortunately these assert failures are hard to debug, they are just
presented as a crashing server for an unkown reason. This is just
inherent to the mock debug server unfortunately. I have to do some
validation in the mock itself, and this is the least worst way to do
it.

After LLDB has connected we check with the mock that it got all
the answers it wanted. Right now, 2 of the symbols should have values
but don't. That should be fixed by #200134.

One symbol is intentionally missing to check that LLDB handles
that situation properly, even when the others have been fixed.

While I was doing this I realised the documentaiton for an unknown
symbol response was wrong. You can cross check this with GDB's:
https://sourceware.org/gdb/current/onlinedocs/gdb.html/General-Query-Packets.html#General-Query-Packets
---
 lldb/docs/resources/lldbgdbremote.md          |  6 +-
 .../gdb_remote_client/TestQSymbol.py          | 83 +++++++++++++++++++
 .../gdb_remote_client/test_qsymbol.yaml       | 33 ++++++++
 3 files changed, 119 insertions(+), 3 deletions(-)
 create mode 100644 lldb/test/API/functionalities/gdb_remote_client/TestQSymbol.py
 create mode 100644 lldb/test/API/functionalities/gdb_remote_client/test_qsymbol.yaml

diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index 6e54f8eccf89d..38b621448f793 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -2095,10 +2095,10 @@ symbol:
 read packet: qSymbol:6578616D706C65
 ```
 
-This should be looked up by LLDB then sent back to the server. Include the name
-again, with the vaue as a hex number:
+This should be looked up by LLDB then sent back to the server. Include the value
+as a hex number, then the name of the symbol:
 ```
-read packet: qSymbol:6578616D706C65:CAFEF00D
+read packet: qSymbol:CAFEF00D:6578616D706C65
 ```
 
 If LLDB cannot find the value, it should respond with only the name. Note that
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestQSymbol.py b/lldb/test/API/functionalities/gdb_remote_client/TestQSymbol.py
new file mode 100644
index 0000000000000..d829ba7fc6409
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestQSymbol.py
@@ -0,0 +1,83 @@
+"""
+Test LLDB's handling of qSymbol sequences.
+"""
+
+from textwrap import dedent
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test.gdbclientutils import *
+from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
+from lldbsuite.support.seven import hexlify, unhexlify
+
+
+class MyResponder(MockGDBServerResponder):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.wanted_symbols = [
+            "main",
+            "local_address",
+            "global_value",
+            "local_value",
+            "not_a_symbol",
+        ]
+        self.last_symbol_request = None
+        self.symbol_results = {}
+
+    def qSymbol(self, args):
+        # args will be <name hex encoded>:<hex value>.
+        # In the initial packet both fields are empty.
+        if args == ":":
+            assert self.last_symbol_request is None
+        else:
+            # Anything else should be a response to our previous request.
+            value, name = args.split(":")
+            name = unhexlify(name)
+            assert name == self.last_symbol_request
+
+            if value:
+                self.symbol_results[name] = int(value, 16)
+
+        if self.wanted_symbols:
+            want = self.wanted_symbols.pop(0)
+            self.last_symbol_request = want
+            self.symbol_results[want] = None
+
+            return "qSymbol:" + hexlify(want)
+
+        # "OK" ends the qSymbol sequence.
+        return "OK"
+
+
+class TestQSymbol(GDBRemoteTestBase):
+    @skipIfRemote
+    @skipIfLLVMTargetMissing("AArch64")
+    def test_qsymbol(self):
+        target = self.createTarget("test_qsymbol.yaml")
+        self.server.responder = MyResponder()
+
+        if self.TraceOn():
+            self.runCmd("log enable gdb-remote packets")
+            self.addTearDownHook(lambda: self.runCmd("log disable gdb-remote packets"))
+
+        # LLDB will send a qSymbol shortly after connecting, which starts the sequence.
+        process = self.connect(target)
+        lldbutil.expect_state_changes(
+            self, self.dbg.GetListener(), process, [lldb.eStateStopped]
+        )
+
+        # LLDB should have responded in some way to all the qSymbol requests.
+        self.assertFalse(self.server.responder.wanted_symbols)
+
+        expected_results = dict(
+            [
+                ("main", 0x1000),
+                ("local_address", 0x1004),
+                # FIXME: Should return a value.
+                ("global_value", None),
+                # FIXME: Should return a value.
+                ("local_value", None),
+                ("not_a_symbol", None),
+            ]
+        )
+        self.assertEqual(expected_results, self.server.responder.symbol_results)
diff --git a/lldb/test/API/functionalities/gdb_remote_client/test_qsymbol.yaml b/lldb/test/API/functionalities/gdb_remote_client/test_qsymbol.yaml
new file mode 100644
index 0000000000000..59f2b07076c10
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/test_qsymbol.yaml
@@ -0,0 +1,33 @@
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_EXEC
+  Machine: EM_AARCH64
+Sections:
+  - Name:  .text
+    Type:  SHT_PROGBITS
+    Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address: 0x1000
+    Content: C0035FD6 # ret
+Symbols:
+  - Name: main
+    Type: STT_FUNC
+    Section: .text
+    Value: 0x1000
+    Size: 4
+    Binding: STB_GLOBAL
+  - Name: local_address
+    Type: STT_NOTYPE
+    Section: .text
+    Value: 0x1004
+  - Name: local_value
+    Type: STT_NOTYPE
+    Index: SHN_ABS
+    Value: 0xabcd
+  - Name: global_value
+    Type: STT_NOTYPE
+    Index: SHN_ABS
+    Value: 0x1234
+    Binding: STB_GLOBAL
+...
\ No newline at end of file

>From 7c8b5ea9283883aaecf3ff0e890199b748cf20ec Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at arm.com>
Date: Fri, 29 May 2026 14:07:25 +0000
Subject: [PATCH 2/2] Add MachO variant

---
 .../gdb_remote_client/TestQSymbol.py          | 16 +++-
 ...est_qsymbol.yaml => test_qsymbol_elf.yaml} |  0
 .../gdb_remote_client/test_qsymbol_macho.yaml | 81 +++++++++++++++++++
 3 files changed, 93 insertions(+), 4 deletions(-)
 rename lldb/test/API/functionalities/gdb_remote_client/{test_qsymbol.yaml => test_qsymbol_elf.yaml} (100%)
 create mode 100644 lldb/test/API/functionalities/gdb_remote_client/test_qsymbol_macho.yaml

diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestQSymbol.py b/lldb/test/API/functionalities/gdb_remote_client/TestQSymbol.py
index d829ba7fc6409..c15b103e5c43a 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestQSymbol.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestQSymbol.py
@@ -50,10 +50,8 @@ def qSymbol(self, args):
 
 
 class TestQSymbol(GDBRemoteTestBase):
-    @skipIfRemote
-    @skipIfLLVMTargetMissing("AArch64")
-    def test_qsymbol(self):
-        target = self.createTarget("test_qsymbol.yaml")
+    def do_test_qsymbol(self, program):
+        target = self.createTarget(program)
         self.server.responder = MyResponder()
 
         if self.TraceOn():
@@ -81,3 +79,13 @@ def test_qsymbol(self):
             ]
         )
         self.assertEqual(expected_results, self.server.responder.symbol_results)
+
+    @skipIfRemote
+    @skipIfLLVMTargetMissing("AArch64")
+    def test_qsymbol_elf(self):
+        self.do_test_qsymbol("test_qsymbol_elf.yaml")
+
+    @skipIfRemote
+    @skipIfLLVMTargetMissing("AArch64")
+    def test_qsymbol_macho(self):
+        self.do_test_qsymbol("test_qsymbol_macho.yaml")
diff --git a/lldb/test/API/functionalities/gdb_remote_client/test_qsymbol.yaml b/lldb/test/API/functionalities/gdb_remote_client/test_qsymbol_elf.yaml
similarity index 100%
rename from lldb/test/API/functionalities/gdb_remote_client/test_qsymbol.yaml
rename to lldb/test/API/functionalities/gdb_remote_client/test_qsymbol_elf.yaml
diff --git a/lldb/test/API/functionalities/gdb_remote_client/test_qsymbol_macho.yaml b/lldb/test/API/functionalities/gdb_remote_client/test_qsymbol_macho.yaml
new file mode 100644
index 0000000000000..25cf1af2bbeb2
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/test_qsymbol_macho.yaml
@@ -0,0 +1,81 @@
+--- !mach-o
+FileHeader:
+  magic:      0xFEEDFACF        # MH_MAGIC_64
+  cputype:    0x0100000C        # CPU_TYPE_ARM64
+  cpusubtype: 0x00000000
+  filetype:   0x00000002        # MH_EXECUTE
+  ncmds:      2
+  sizeofcmds: 176
+  flags:      0x00000085        # MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL
+  reserved:   0x00000000
+
+LoadCommands:
+  - cmd:      LC_SEGMENT_64
+    cmdsize:  152
+    segname:  __TEXT
+    vmaddr:   0x1000
+    vmsize:   0x1000
+    fileoff:  0
+    filesize: 0x1000
+    maxprot:  5
+    initprot: 5
+    nsects:   1
+    flags:    0
+    Sections:
+      - sectname:  __text
+        segname:   __TEXT
+        addr:      0x1000
+        size:      4
+        offset:    0x1000
+        align:     2
+        reloff:    0
+        nreloc:    0
+        flags:     0x80000400   # S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS
+        reserved1: 0
+        reserved2: 0
+        reserved3: 0
+        content:   C0035FD6     # ret
+
+  - cmd:      LC_SYMTAB
+    cmdsize:  24
+    symoff:   0x1008
+    nsyms:    4
+    stroff:   0x1048
+    strsize:  53
+
+LinkEditData:
+  NameList:
+    # _local_address, N_SECT
+    - n_strx:  1
+      n_type:  0x0e
+      n_sect:  1
+      n_desc:  0
+      n_value: 0x1004
+
+    # _local_value, N_ABS
+    - n_strx:  16
+      n_type:  0x02
+      n_sect:  0
+      n_desc:  0
+      n_value: 0xabcd
+
+    # _main, N_SECT | N_EXT
+    - n_strx:  29
+      n_type:  0x0f
+      n_sect:  1
+      n_desc:  0
+      n_value: 0x1000
+
+    # _global_value, N_ABS | N_EXT
+    - n_strx:  35
+      n_type:  0x03
+      n_sect:  0
+      n_desc:  0
+      n_value: 0x1234
+
+  StringTable:
+    - _local_address
+    - _local_value
+    - _main
+    - _global_value
+...



More information about the lldb-commits mailing list