Skip to content

Commit bc0cc10

Browse files
committed
[lldb][AArch64] Annotate synchronous tag faults
In the latest Linux kernels synchronous tag faults include the tag bits in their address. This change adds logical and allocation tags to the description of synchronous tag faults. (asynchronous faults have no address) Process 1626 stopped * thread #1, name = 'a.out', stop reason = signal SIGSEGV: sync tag check fault (fault address: 0x900fffff7ff9010 logical tag: 0x9 allocation tag: 0x0) This extends the existing description and will show as much as it can on the rare occasion something fails. This change supports AArch64 MTE only but other architectures could be added by extending the switch at the start of AnnotateSyncTagCheckFault. The rest of the function is generic code. Tests have been added for synchronous and asynchronous MTE faults. Reviewed By: omjavaid Differential Revision: https://reviews.llvm.org/D105178 (cherry picked from commit d510b5f)
1 parent dc00e19 commit bc0cc10

File tree

5 files changed

+189
-0
lines changed

5 files changed

+189
-0
lines changed

lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "llvm/ADT/SmallString.h"
2727

2828
#include "Plugins/Process/POSIX/CrashReason.h"
29+
#include "Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h"
2930

3031
#include <sys/syscall.h>
3132
// Try to define a macro to encapsulate the tgkill syscall
@@ -299,11 +300,69 @@ void NativeThreadLinux::SetStoppedBySignal(uint32_t signo,
299300
? CrashReason::eInvalidAddress
300301
: GetCrashReason(*info);
301302
m_stop_description = GetCrashReasonString(reason, *info);
303+
304+
if (reason == CrashReason::eSyncTagCheckFault) {
305+
AnnotateSyncTagCheckFault(info);
306+
}
307+
302308
break;
303309
}
304310
}
305311
}
306312

313+
void NativeThreadLinux::AnnotateSyncTagCheckFault(const siginfo_t *info) {
314+
int32_t allocation_tag_type = 0;
315+
switch (GetProcess().GetArchitecture().GetMachine()) {
316+
// aarch64_32 deliberately not here because there's no 32 bit MTE
317+
case llvm::Triple::aarch64:
318+
case llvm::Triple::aarch64_be:
319+
allocation_tag_type = MemoryTagManagerAArch64MTE::eMTE_allocation;
320+
break;
321+
default:
322+
return;
323+
}
324+
325+
auto details =
326+
GetRegisterContext().GetMemoryTaggingDetails(allocation_tag_type);
327+
if (!details) {
328+
llvm::consumeError(details.takeError());
329+
return;
330+
}
331+
332+
// We assume that the stop description is currently:
333+
// signal SIGSEGV: sync tag check fault (fault address: <addr>)
334+
// Remove the closing )
335+
m_stop_description.pop_back();
336+
337+
std::stringstream ss;
338+
lldb::addr_t fault_addr = reinterpret_cast<uintptr_t>(info->si_addr);
339+
std::unique_ptr<MemoryTagManager> manager(std::move(details->manager));
340+
341+
ss << " logical tag: 0x" << std::hex << manager->GetLogicalTag(fault_addr);
342+
343+
std::vector<uint8_t> allocation_tag_data;
344+
// The fault address may not be granule aligned. ReadMemoryTags will granule
345+
// align any range you give it, potentially making it larger.
346+
// To prevent this set len to 1. This always results in a range that is at
347+
// most 1 granule in size and includes fault_addr.
348+
Status status = GetProcess().ReadMemoryTags(allocation_tag_type, fault_addr,
349+
1, allocation_tag_data);
350+
351+
if (status.Success()) {
352+
llvm::Expected<std::vector<lldb::addr_t>> allocation_tag =
353+
manager->UnpackTagsData(allocation_tag_data, 1);
354+
if (allocation_tag) {
355+
ss << " allocation tag: 0x" << std::hex << allocation_tag->front() << ")";
356+
} else {
357+
llvm::consumeError(allocation_tag.takeError());
358+
ss << ")";
359+
}
360+
} else
361+
ss << ")";
362+
363+
m_stop_description += ss.str();
364+
}
365+
307366
bool NativeThreadLinux::IsStopped(int *signo) {
308367
if (!StateIsStoppedState(m_state, false))
309368
return false;

lldb/source/Plugins/Process/Linux/NativeThreadLinux.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ class NativeThreadLinux : public NativeThreadProtocol {
102102

103103
void SetStopped();
104104

105+
/// Extend m_stop_description with logical and allocation tag values.
106+
/// If there is an error along the way just add the information we were able
107+
/// to get.
108+
void AnnotateSyncTagCheckFault(const siginfo_t *info);
109+
105110
// Member Variables
106111
lldb::StateType m_state;
107112
ThreadStopInfo m_stop_info;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
C_SOURCES := main.c
2+
CFLAGS_EXTRAS := -march=armv8.5-a+memtag
3+
4+
include Makefile.rules
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""
2+
Test reporting of MTE tag access faults.
3+
"""
4+
5+
6+
import lldb
7+
from lldbsuite.test.decorators import *
8+
from lldbsuite.test.lldbtest import *
9+
from lldbsuite.test import lldbutil
10+
11+
12+
class AArch64LinuxMTEMemoryTagFaultsTestCase(TestBase):
13+
14+
mydir = TestBase.compute_mydir(__file__)
15+
16+
NO_DEBUG_INFO_TESTCASE = True
17+
18+
def setup_mte_test(self, fault_type):
19+
if not self.isAArch64MTE():
20+
self.skipTest('Target must support MTE.')
21+
22+
self.build()
23+
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
24+
25+
lldbutil.run_break_set_by_file_and_line(self, "main.c",
26+
line_number('main.c', '// Breakpoint here'),
27+
num_expected_locations=1)
28+
29+
self.runCmd("run {}".format(fault_type), RUN_SUCCEEDED)
30+
31+
if self.process().GetState() == lldb.eStateExited:
32+
self.fail("Test program failed to run.")
33+
34+
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
35+
substrs=['stopped',
36+
'stop reason = breakpoint'])
37+
38+
@skipUnlessArch("aarch64")
39+
@skipUnlessPlatform(["linux"])
40+
@skipUnlessAArch64MTELinuxCompiler
41+
def test_mte_tag_fault_sync(self):
42+
self.setup_mte_test("sync")
43+
# The logical tag should be included in the fault address
44+
# and we know what the bottom byte should be.
45+
# It will be 0x10 (to be in the 2nd granule), +1 to be 0x11.
46+
# Which tests that lldb-server handles fault addresses that
47+
# are not granule aligned.
48+
self.expect("continue",
49+
patterns=[
50+
"\* thread #1, name = 'a.out', stop reason = signal SIGSEGV: "
51+
"sync tag check fault \(fault address: 0x9[0-9A-Fa-f]+11\ "
52+
"logical tag: 0x9 allocation tag: 0xa\)"])
53+
54+
@skipUnlessArch("aarch64")
55+
@skipUnlessPlatform(["linux"])
56+
@skipUnlessAArch64MTELinuxCompiler
57+
def test_mte_tag_fault_async(self):
58+
self.setup_mte_test("async")
59+
self.expect("continue",
60+
substrs=[
61+
"* thread #1, name = 'a.out', stop reason = "
62+
"signal SIGSEGV: async tag check fault"])
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <arm_acle.h>
2+
#include <asm/hwcap.h>
3+
#include <asm/mman.h>
4+
#include <stdbool.h>
5+
#include <string.h>
6+
#include <sys/auxv.h>
7+
#include <sys/mman.h>
8+
#include <sys/prctl.h>
9+
#include <unistd.h>
10+
11+
// Set bits 59-56 to tag, removing any existing tag
12+
static char *set_tag(char *ptr, size_t tag) {
13+
return (char *)(((size_t)ptr & ~((size_t)0xf << 56)) | (tag << 56));
14+
}
15+
16+
int main(int argc, char const *argv[]) {
17+
// We assume that the test runner has checked we're on an MTE system
18+
19+
// Only expect to get the fault type
20+
if (argc != 2)
21+
return 1;
22+
23+
unsigned long prctl_arg2 = 0;
24+
if (!strcmp(argv[1], "sync"))
25+
prctl_arg2 = PR_MTE_TCF_SYNC;
26+
else if (!strcmp(argv[1], "async"))
27+
prctl_arg2 = PR_MTE_TCF_ASYNC;
28+
else
29+
return 1;
30+
31+
// Set fault type
32+
if (prctl(PR_SET_TAGGED_ADDR_CTRL, prctl_arg2, 0, 0, 0))
33+
return 1;
34+
35+
// Allocate some memory with tagging enabled that we
36+
// can read/write if we use correct tags.
37+
char *buf = mmap(0, sysconf(_SC_PAGESIZE), PROT_MTE | PROT_READ | PROT_WRITE,
38+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
39+
if (buf == MAP_FAILED)
40+
return 1;
41+
42+
// Our pointer will have tag 9
43+
char *tagged_buf = set_tag(buf, 9);
44+
// Set allocation tags for the first 2 granules
45+
__arm_mte_set_tag(set_tag(tagged_buf, 9));
46+
__arm_mte_set_tag(set_tag(tagged_buf + 16, 10));
47+
48+
// Confirm that we can write when tags match
49+
*tagged_buf = ' ';
50+
51+
// Breakpoint here
52+
// Faults because tag 9 in the ptr != allocation tag of 10.
53+
// + 16 puts us in the second granule and +1 makes the fault address
54+
// misaligned relative to the granule size. This misalignment must
55+
// be accounted for by lldb-server.
56+
*(tagged_buf + 16 + 1) = '?';
57+
58+
return 0;
59+
}

0 commit comments

Comments
 (0)