Skip to content

Commit eb7dc99

Browse files
authored
[lldb] Add SBValue::GetValueAsAddress API (#90144)
I previously added this API via https://reviews.llvm.org/D142792 in 2023, along with changes to the ValueObject class to treat pointer types as addresses, and to annotate those ValueObjects with the original uint64_t byte sequence AND the name of the symbol once stripped, if that points to a symbol. I did this unconditionally for all pointer type ValueObjects, and it caused several regressions in the Objective-C data formatters which have a ValueObject of an object, it has the address of its class -- but with ObjC, sometimes it is a "tagged pointer" which is metadata, not an actual pointer. (e.g. a small NSInteger value is stored entirely in the tagged pointer, instead of a separate object) Treating these not-addresses as addresses -- clearing the non-addressable-bits -- is invalid. The original version of this patch we're using downstream only does this bits clearing for pointer types that are specifically decorated with the pointerauth typequal, but not all of those clang changes are upstreamed to github main yet, so I tried this simpler approach and hit the tagged pointer issue and bailed on the whole patch. This patch, however, is simply adding SBValue::GetValueAsAddress so script writers who know that an SBValue has an address in memory, can strip off any metadata. It's an important API to have for script writers when AArch64 ptrauth is in use, so I'm going to put this part of the patch back on github main now until we can get the rest of that original patch upstreamed.
1 parent 5f67ce5 commit eb7dc99

File tree

6 files changed

+130
-0
lines changed

6 files changed

+130
-0
lines changed

lldb/bindings/interface/SBValueDocstrings.i

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,26 @@ linked list."
135135
%feature("docstring", "Expands nested expressions like .a->b[0].c[1]->d."
136136
) lldb::SBValue::GetValueForExpressionPath;
137137

138+
%feature("docstring", "
139+
Return the value as an address. On failure, LLDB_INVALID_ADDRESS
140+
will be returned. On architectures like AArch64, where the
141+
top (unaddressable) bits can be used for authentication,
142+
memory tagging, or top byte ignore, this method will return
143+
the value with those top bits cleared.
144+
145+
GetValueAsUnsigned returns the actual value, with the
146+
authentication/Top Byte Ignore/Memory Tagging Extension bits.
147+
148+
Calling this on a random value which is not a pointer is
149+
incorrect. Call GetType().IsPointerType() if in doubt.
150+
151+
An SB API program may want to show both the literal byte value
152+
and the address it refers to in memory. These two SBValue
153+
methods allow SB API writers to behave appropriately for their
154+
interface."
155+
) lldb::SBValue::GetValueAsAddress;
156+
157+
138158
%feature("doctstring", "
139159
Returns the number for children.
140160

lldb/include/lldb/API/SBValue.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class LLDB_API SBValue {
6868

6969
uint64_t GetValueAsUnsigned(uint64_t fail_value = 0);
7070

71+
lldb::addr_t GetValueAsAddress();
72+
7173
ValueType GetValueType();
7274

7375
// If you call this on a newly created ValueObject, it will always return

lldb/source/API/SBValue.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,25 @@ uint64_t SBValue::GetValueAsUnsigned(uint64_t fail_value) {
909909
return fail_value;
910910
}
911911

912+
lldb::addr_t SBValue::GetValueAsAddress() {
913+
addr_t fail_value = LLDB_INVALID_ADDRESS;
914+
ValueLocker locker;
915+
lldb::ValueObjectSP value_sp(GetSP(locker));
916+
if (value_sp) {
917+
bool success = true;
918+
uint64_t ret_val = fail_value;
919+
ret_val = value_sp->GetValueAsUnsigned(fail_value, &success);
920+
if (!success)
921+
return fail_value;
922+
ProcessSP process_sp = m_opaque_sp->GetProcessSP();
923+
if (!process_sp)
924+
return ret_val;
925+
return process_sp->FixDataAddress(ret_val);
926+
}
927+
928+
return fail_value;
929+
}
930+
912931
bool SBValue::MightHaveChildren() {
913932
LLDB_INSTRUMENT_VA(this);
914933

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: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Test that SBValue clears non-addressable bits"""
2+
3+
import lldb
4+
from lldbsuite.test.decorators import *
5+
from lldbsuite.test.lldbtest import *
6+
from lldbsuite.test import lldbutil
7+
8+
9+
class TestClearSBValueNonAddressableBits(TestBase):
10+
NO_DEBUG_INFO_TESTCASE = True
11+
12+
# On AArch64 systems, the top bits that are not used for
13+
# addressing may be used for TBI, MTE, and/or pointer
14+
# authentication.
15+
@skipIf(archs=no_match(["aarch64", "arm64", "arm64e"]))
16+
17+
# Only run this test on systems where TBI is known to be
18+
# enabled, so the address mask will clear the TBI bits.
19+
@skipUnlessPlatform(["linux"] + lldbplatformutil.getDarwinOSTriples())
20+
def test(self):
21+
self.source = "main.c"
22+
self.build()
23+
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
24+
self, "break here", lldb.SBFileSpec(self.source, False)
25+
)
26+
27+
if self.TraceOn():
28+
self.runCmd("frame variable")
29+
self.runCmd("frame variable &count &global")
30+
31+
frame = thread.GetFrameAtIndex(0)
32+
33+
count_p = frame.FindVariable("count_p")
34+
count_invalid_p = frame.FindVariable("count_invalid_p")
35+
self.assertEqual(
36+
count_p.GetValueAsUnsigned(), count_invalid_p.GetValueAsAddress()
37+
)
38+
self.assertNotEqual(
39+
count_invalid_p.GetValueAsUnsigned(), count_invalid_p.GetValueAsAddress()
40+
)
41+
self.assertEqual(5, count_p.Dereference().GetValueAsUnsigned())
42+
self.assertEqual(5, count_invalid_p.Dereference().GetValueAsUnsigned())
43+
44+
global_p = frame.FindVariable("global_p")
45+
global_invalid_p = frame.FindVariable("global_invalid_p")
46+
self.assertEqual(
47+
global_p.GetValueAsUnsigned(), global_invalid_p.GetValueAsAddress()
48+
)
49+
self.assertNotEqual(
50+
global_invalid_p.GetValueAsUnsigned(), global_invalid_p.GetValueAsAddress()
51+
)
52+
self.assertEqual(10, global_p.Dereference().GetValueAsUnsigned())
53+
self.assertEqual(10, global_invalid_p.Dereference().GetValueAsUnsigned())
54+
55+
main_p = frame.FindVariable("main_p")
56+
main_invalid_p = frame.FindVariable("main_invalid_p")
57+
self.assertEqual(
58+
main_p.GetValueAsUnsigned(), main_invalid_p.GetValueAsAddress()
59+
)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include <stdint.h>
2+
3+
int global = 10;
4+
5+
int main() {
6+
int count = 5;
7+
int *count_p = &count;
8+
9+
// Add some metadata in the top byte (this will crash unless the
10+
// test is running with TBI enabled, but we won't dereference it)
11+
12+
intptr_t scratch = (intptr_t)count_p;
13+
scratch |= (3ULL << 60);
14+
int *count_invalid_p = (int *)scratch;
15+
16+
int (*main_p)() = main;
17+
scratch = (intptr_t)main_p;
18+
scratch |= (3ULL << 60);
19+
int (*main_invalid_p)() = (int (*)())scratch;
20+
21+
int *global_p = &global;
22+
scratch = (intptr_t)global_p;
23+
scratch |= (3ULL << 60);
24+
int *global_invalid_p = (int *)scratch;
25+
26+
return count; // break here
27+
}

0 commit comments

Comments
 (0)