Skip to content

Commit 69ffbe4

Browse files
committed
Set a default number of address bits on Darwin arm64 systems
With arm64e ARMv8.3 pointer authentication, lldb needs to know how many bits are used for addressing and how many are used for pointer auth signing. This should be determined dynamically from the inferior system / corefile, but there are some workflows where it still isn't recorded and we fall back on a default value that is correct on some Darwin environments. This patch also explicitly sets the vendor of mach-o binaries to Apple, so we select an Apple ABI instead of a random other ABI. It adds a function pointer formatter for systems where pointer authentication is in use, and we can strip the ptrauth bits off of the function pointer address and get a different value that points to an actual symbol. Differential Revision: https://reviews.llvm.org/D115431 rdar://84644661 (cherry picked from commit 223e8ca)
1 parent 93b3b00 commit 69ffbe4

File tree

8 files changed

+240
-13
lines changed

8 files changed

+240
-13
lines changed

lldb/source/DataFormatters/CXXFunctionPointer.cpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "lldb/DataFormatters/CXXFunctionPointer.h"
1010

1111
#include "lldb/Core/ValueObject.h"
12+
#include "lldb/Target/ABI.h"
1213
#include "lldb/Target/SectionLoadList.h"
1314
#include "lldb/Target/Target.h"
1415
#include "lldb/Utility/Stream.h"
@@ -38,8 +39,34 @@ bool lldb_private::formatters::CXXFunctionPointerSummaryProvider(
3839
Address so_addr;
3940
Target *target = exe_ctx.GetTargetPtr();
4041
if (target && !target->GetSectionLoadList().IsEmpty()) {
41-
if (target->GetSectionLoadList().ResolveLoadAddress(func_ptr_address,
42-
so_addr)) {
42+
target->GetSectionLoadList().ResolveLoadAddress(func_ptr_address,
43+
so_addr);
44+
if (so_addr.GetSection() == nullptr) {
45+
// If we have an address that doesn't correspond to any symbol,
46+
// it might have authentication bits. Strip them & see if it
47+
// now points to a symbol -- if so, do the SymbolContext lookup
48+
// based on the stripped address.
49+
// If we find a symbol with the ptrauth bits stripped, print the
50+
// raw value into the stream, and replace the Address with the
51+
// one that points to a symbol for a fuller description.
52+
if (Process *process = exe_ctx.GetProcessPtr()) {
53+
if (ABISP abi_sp = process->GetABI()) {
54+
addr_t fixed_addr = abi_sp->FixCodeAddress(func_ptr_address);
55+
if (fixed_addr != func_ptr_address) {
56+
Address test_address;
57+
test_address.SetLoadAddress(fixed_addr, target);
58+
if (test_address.GetSection() != nullptr) {
59+
int addrsize = target->GetArchitecture().GetAddressByteSize();
60+
sstr.Printf("actual=0x%*.*" PRIx64 " ", addrsize * 2,
61+
addrsize * 2, fixed_addr);
62+
so_addr = test_address;
63+
}
64+
}
65+
}
66+
}
67+
}
68+
69+
if (so_addr.IsValid()) {
4370
so_addr.Dump(&sstr, exe_ctx.GetBestExecutionContextScope(),
4471
Address::DumpStyleResolvedDescription,
4572
Address::DumpStyleSectionNameOffset);

lldb/source/Plugins/ABI/AArch64/ABIMacOSX_arm64.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,16 @@ ValueObjectSP ABIMacOSX_arm64::GetReturnValueObjectImpl(
817817

818818
lldb::addr_t ABIMacOSX_arm64::FixAddress(addr_t pc, addr_t mask) {
819819
lldb::addr_t pac_sign_extension = 0x0080000000000000ULL;
820+
// Darwin systems originally couldn't determine the proper value
821+
// dynamically, so the most common value was hardcoded. This has
822+
// largely been cleaned up, but there are still a handful of
823+
// environments that assume the default value is set to this value
824+
// and there's no dynamic value to correct it.
825+
// When no mask is specified, set it to 39 bits of addressing (0..38).
826+
if (mask == 0) {
827+
// ~((1ULL<<39)-1)
828+
mask = 0xffffff8000000000;
829+
}
820830
return (pc & pac_sign_extension) ? pc | mask : pc & (~mask);
821831
}
822832

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5216,17 +5216,7 @@ void ObjectFileMachO::GetAllArchSpecs(const llvm::MachO::mach_header &header,
52165216
}
52175217

52185218
if (!found_any) {
5219-
if (header.filetype == MH_KEXT_BUNDLE) {
5220-
base_triple.setVendor(llvm::Triple::Apple);
5221-
add_triple(base_triple);
5222-
} else {
5223-
// We didn't find a LC_VERSION_MIN load command and this isn't a KEXT
5224-
// so lets not say our Vendor is Apple, leave it as an unspecified
5225-
// unknown.
5226-
base_triple.setVendor(llvm::Triple::UnknownVendor);
5227-
base_triple.setVendorName(llvm::StringRef());
5228-
add_triple(base_triple);
5229-
}
5219+
add_triple(base_triple);
52305220
}
52315221
}
52325222

lldb/source/Utility/ArchSpec.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,7 @@ bool ArchSpec::SetArchitecture(ArchitectureType arch_type, uint32_t cpu,
859859
m_triple.setArchName(llvm::StringRef(core_def->name));
860860
if (arch_type == eArchTypeMachO) {
861861
m_triple.setVendor(llvm::Triple::Apple);
862+
m_triple.setObjectFormat(llvm::Triple::MachO);
862863

863864
// Don't set the OS. It could be simulator, macosx, ios, watchos,
864865
// tvos, bridgeos. We could get close with the cpu type - but we
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
C_SOURCES := main.c
2+
3+
# compile a.out and create-corefile
4+
# create-corefile will create a custom corefile using the symbols
5+
# addresses from the a.out binary.
6+
all: a.out create-corefile
7+
8+
create-corefile:
9+
$(MAKE) -f $(MAKEFILE_RULES) EXE=create-corefile \
10+
C_SOURCES=create-corefile.c
11+
12+
include Makefile.rules
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Test that lldb has a default mask for addressable bits on Darwin arm64 ABI"""
2+
3+
4+
import os
5+
import re
6+
import subprocess
7+
8+
import lldb
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test.lldbtest import *
11+
from lldbsuite.test import lldbutil
12+
13+
14+
class TestCorefileDefaultPtrauth(TestBase):
15+
16+
mydir = TestBase.compute_mydir(__file__)
17+
18+
@skipIf(debug_info=no_match(["dsym"]), bugnumber="This test is looking explicitly for a dSYM")
19+
@skipIf(archs=no_match(['arm64','arm64e']))
20+
@skipUnlessDarwin
21+
def test_lc_note(self):
22+
self.build()
23+
self.test_exe = self.getBuildArtifact("a.out")
24+
self.create_corefile = self.getBuildArtifact("create-corefile")
25+
self.corefile = self.getBuildArtifact("core")
26+
27+
### Create our corefile
28+
retcode = call(self.create_corefile + " " + self.test_exe + " " + self.corefile, shell=True)
29+
30+
## This corefile has no metadata telling us how many bits are
31+
## used for ptrauth signed function pointers. We will need lldb
32+
## to fall back on its old default value for Darwin arm64 ABIs
33+
## to correctly strip the bits.
34+
35+
self.target = self.dbg.CreateTarget('')
36+
err = lldb.SBError()
37+
self.process = self.target.LoadCore(self.corefile)
38+
self.assertEqual(self.process.IsValid(), True)
39+
modspec = lldb.SBModuleSpec()
40+
modspec.SetFileSpec(lldb.SBFileSpec(self.test_exe, True))
41+
m = self.target.AddModule(modspec)
42+
self.assertTrue(m.IsValid())
43+
self.target.SetModuleLoadAddress (m, 0)
44+
45+
# target variable should show us both the actual function
46+
# pointer with ptrauth bits and the symbol it resolves to,
47+
# with the ptrauth bits stripped, e.g.
48+
# (int (*)(...)) fmain = 0xe46bff0100003f90 (actual=0x0000000100003f90 a.out`main at main.c:3)
49+
50+
self.expect("target variable fmain", substrs=['fmain = 0x', '(actual=0x', 'main at main.c'])
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#include <mach-o/loader.h>
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <mach/machine.h>
5+
#include <string.h>
6+
#include <mach/machine/thread_state.h>
7+
#include <inttypes.h>
8+
#include <sys/syslimits.h>
9+
10+
// Given an executable binary with
11+
// "fmain" (a function pointer to main)
12+
// "main"
13+
// symbols, create a fake arm64e corefile that
14+
// contains a memory segment for the fmain
15+
// function pointer, with the value of the
16+
// address of main() with ptrauth bits masked on.
17+
//
18+
// The corefile does not include the "addrable bits"
19+
// LC_NOTE, so lldb will need to fall back on its
20+
// default value from the Darwin arm64 ABI.
21+
22+
int main(int argc, char **argv)
23+
{
24+
if (argc != 3) {
25+
fprintf (stderr, "usage: %s executable-binary output-file\n", argv[0]);
26+
exit(1);
27+
}
28+
FILE *exe = fopen(argv[1], "r");
29+
if (!exe) {
30+
fprintf (stderr, "Unable to open executable %s for reading\n", argv[1]);
31+
exit(1);
32+
}
33+
FILE *out = fopen(argv[2], "w");
34+
if (!out) {
35+
fprintf (stderr, "Unable to open %s for writing\n", argv[2]);
36+
exit(1);
37+
}
38+
39+
char buf[PATH_MAX + 6];
40+
sprintf (buf, "nm '%s'", argv[1]);
41+
FILE *nm = popen(buf, "r");
42+
if (!nm) {
43+
fprintf (stderr, "Unable to run nm on '%s'", argv[1]);
44+
exit (1);
45+
}
46+
uint64_t main_addr = 0;
47+
uint64_t fmain_addr = 0;
48+
while (fgets (buf, sizeof(buf), nm)) {
49+
if (strstr (buf, "_fmain")) {
50+
fmain_addr = strtoul (buf, NULL, 16);
51+
}
52+
if (strstr (buf, "_main")) {
53+
main_addr = strtoul (buf, NULL, 16);
54+
}
55+
}
56+
pclose (nm);
57+
58+
if (main_addr == 0 || fmain_addr == 0) {
59+
fprintf(stderr, "Unable to find address of main or fmain in %s.\n",
60+
argv[1]);
61+
exit (1);
62+
}
63+
64+
// Write out a corefile with contents in this order:
65+
// 1. mach header
66+
// 2. LC_THREAD load command
67+
// 3. LC_SEGMENT_64 load command
68+
// 4. memory segment contents
69+
70+
// struct thread_command {
71+
// uint32_t cmd;
72+
// uint32_t cmdsize;
73+
// uint32_t flavor
74+
// uint32_t count
75+
// struct XXX_thread_state state
76+
int size_of_thread_cmd = 4 + 4 + 4 + 4 + sizeof (arm_thread_state64_t);
77+
78+
struct mach_header_64 mh;
79+
mh.magic = 0xfeedfacf;
80+
mh.cputype = CPU_TYPE_ARM64;
81+
mh.cpusubtype = CPU_SUBTYPE_ARM64E;
82+
mh.filetype = MH_CORE;
83+
mh.ncmds = 2; // LC_THREAD, LC_SEGMENT_64
84+
mh.sizeofcmds = size_of_thread_cmd + sizeof(struct segment_command_64);
85+
mh.flags = 0;
86+
mh.reserved = 0;
87+
88+
fwrite(&mh, sizeof (mh), 1, out);
89+
90+
struct segment_command_64 seg;
91+
seg.cmd = LC_SEGMENT_64;
92+
seg.cmdsize = sizeof(seg);
93+
memset (&seg.segname, 0, 16);
94+
seg.vmaddr = fmain_addr;
95+
seg.vmsize = 8;
96+
// Offset to segment contents
97+
seg.fileoff = sizeof (mh) + size_of_thread_cmd + sizeof(seg);
98+
seg.filesize = 8;
99+
seg.maxprot = 3;
100+
seg.initprot = 3;
101+
seg.nsects = 0;
102+
seg.flags = 0;
103+
104+
fwrite (&seg, sizeof (seg), 1, out);
105+
106+
uint32_t cmd = LC_THREAD;
107+
fwrite (&cmd, sizeof (cmd), 1, out);
108+
uint32_t cmdsize = size_of_thread_cmd;
109+
fwrite (&cmdsize, sizeof (cmdsize), 1, out);
110+
uint32_t flavor = ARM_THREAD_STATE64;
111+
fwrite (&flavor, sizeof (flavor), 1, out);
112+
// count is number of uint32_t's of the register context
113+
uint32_t count = sizeof (arm_thread_state64_t) / 4;
114+
fwrite (&count, sizeof (count), 1, out);
115+
arm_thread_state64_t regstate;
116+
memset (&regstate, 0, sizeof (regstate));
117+
fwrite (&regstate, sizeof (regstate), 1, out);
118+
119+
120+
// Or together a random PAC value from a system using 39 bits
121+
// of addressing with the address of main(). lldb will need
122+
// to correctly strip off the high bits to find the address of
123+
// main.
124+
uint64_t segment_contents = 0xe46bff0000000000 | main_addr;
125+
126+
fwrite (&segment_contents, sizeof (segment_contents), 1, out);
127+
128+
fclose (out);
129+
130+
exit (0);
131+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
int main();
2+
int (*fmain)() = main;
3+
int main () {
4+
return fmain();
5+
}
6+

0 commit comments

Comments
 (0)