Skip to content

Commit 501c657

Browse files
authored
macOS debuginfo: add support for using atos to get file:line info in backtraces. (#4291)
* macOS debuginfo: add support for using `atos` to get file:line info in backtraces. Resolves issue #3864 * Return to LLVM default of emitting __debug_line section in __DWARF segment as a debug section (not regular) * Re-enable test now that issue #3280 is fixed. * Allow full path names in exception file:line debug output. * Allow full path names in exception file:line debug output. * Add changelog entry
1 parent 8006b95 commit 501c657

File tree

6 files changed

+170
-8
lines changed

6 files changed

+170
-8
lines changed

.github/workflows/supported_llvm_versions.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,6 @@ jobs:
147147
excludes='dmd-testsuite|lit-tests|ldc2-unittest'
148148
if [[ '${{ runner.os }}' == macOS ]]; then
149149
N=$(sysctl -n hw.logicalcpu)
150-
# https://github.com/ldc-developers/ldc/issues/3280
151-
excludes+='|druntime-test-exceptions-debug'
152150
else
153151
N=$(nproc)
154152
fi

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#### Big news
44
- Bit fields support. (#4015)
5+
- macOS on Apple M1: linking with `-g` is working again without unaligned pointer warnings/errors. This fixes file:line debug information in exception backtraces (requiring `atos`, a macOS development tool installed with Xcode), without the need to set MACOSX_DEPLOYMENT_TARGET=11 and using a modified LLVM. (#4291)
56

67
#### Platform support
78
- Initial ABI support for 64-bit RISC-V. (#4007)

driver/targetmachine.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@
4646
// LDC-LLVM >= 6.0.1:
4747
// On Mac, emit __debug_line section in __DWARF segment as regular (non-debug)
4848
// section, like DMD, to enable file/line infos in backtraces. See
49-
// https://github.com/dlang/dmd/commit/2bf7d0db29416eacbb01a91e6502140e354ee0ef.
49+
// https://github.com/dlang/dmd/commit/2bf7d0db29416eacbb01a91e6502140e354ee0ef
50+
// https://github.com/ldc-developers/llvm-project/commit/110deda1bc1cf195983fea8c1107886057987955
5051
static llvm::cl::opt<bool, true> preserveDwarfLineSection(
5152
"preserve-dwarf-line-section",
5253
llvm::cl::desc("Mac: preserve DWARF line section during linking for "
5354
"file/line infos in backtraces. Defaults to true."),
5455
llvm::cl::Hidden, llvm::cl::ZeroOrMore,
5556
llvm::cl::location(ldc::emitMachODwarfLineAsRegularSection),
56-
llvm::cl::init(true));
57+
llvm::cl::init(false));
5758
#endif
5859

5960
static const char *getABI(const llvm::Triple &triple) {

runtime/druntime/src/core/internal/backtrace/dwarf.d

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData,
250250
{
251251
if (debugLineSectionData)
252252
resolveAddresses(debugLineSectionData, locations, baseAddress);
253+
version (Darwin)
254+
{
255+
if (!debugLineSectionData)
256+
resolveAddressesWithAtos(locations);
257+
}
253258

254259
TraceInfoBuffer buffer;
255260
foreach (idx, const ref loc; locations)
@@ -268,6 +273,163 @@ int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData,
268273
return 0;
269274
}
270275

276+
version (Darwin)
277+
{
278+
/**
279+
* Resolve the addresses of `locations` using `atos` (executable that ships with XCode)
280+
*
281+
* Spawns a child process that calls `atos`. Communication is through stdin/stdout pipes.
282+
*
283+
* After this function successfully completes, `locations` will contain
284+
* file / lines informations.
285+
*
286+
* The lifetime of the `Location` data surpases function return (strndup is used).
287+
*
288+
* Params:
289+
* locations = The locations to resolve
290+
*/
291+
private void resolveAddressesWithAtos(Location[] locations) @nogc nothrow
292+
{
293+
import core.stdc.stdio : fclose, fflush, fgets, fprintf, printf, snprintf;
294+
import core.stdc.stdlib : exit;
295+
import core.sys.posix.stdio : fdopen;
296+
import core.sys.posix.unistd : close, dup2, execlp, fork, getpid, pipe;
297+
298+
// Create in/out pipes to communicate with the forked exec
299+
int[2] dummy_pipes; // these dummy pipes are there to prevent funny issues when stdin/stdout is closed and pipe returns id 0 or 1
300+
int[2] pipes_to_atos;
301+
int[2] pipes_from_atos;
302+
if ( pipe(dummy_pipes) < 0 || pipe(pipes_to_atos) < 0 || pipe(pipes_from_atos) < 0 ) {
303+
printf("some pipe creation error!\n");
304+
return;
305+
}
306+
close(dummy_pipes[0]);
307+
close(dummy_pipes[1]);
308+
auto write_to_atos = pipes_to_atos[1];
309+
auto read_from_atos = pipes_from_atos[0];
310+
auto atos_stdin = pipes_to_atos[0];
311+
auto atos_stdout = pipes_from_atos[1];
312+
313+
auto self_pid = cast(int) getpid();
314+
315+
// Spawn a child process that calls atos, reads/writes from the pipes, and then exits.
316+
auto child_id = fork();
317+
if (child_id == -1)
318+
{
319+
printf("some fork error!\n");
320+
return;
321+
}
322+
else if (child_id == 0)
323+
{
324+
// We are in the child process, spawn atos and link pipes
325+
326+
// Close unused read/write ends of pipes
327+
close(write_to_atos);
328+
close(read_from_atos);
329+
330+
// Link pipes to stdin/stdout
331+
dup2(atos_stdin, 0);
332+
close(atos_stdin);
333+
dup2(atos_stdout, 1);
334+
close(atos_stdout);
335+
336+
char[10] pid_str;
337+
snprintf(pid_str.ptr, pid_str.sizeof, "%d", cast(int) self_pid);
338+
const(char)* atos_executable = "atos";
339+
const(char)* atos_p_arg = "-p";
340+
const(char)* atos_fullpath_arg = "-fullPath";
341+
execlp(atos_executable, atos_executable, atos_fullpath_arg, atos_p_arg, pid_str.ptr, null);
342+
343+
// If exec returns, an error occurred, need to exit the forked process here.
344+
printf("some exec error!\n");
345+
exit(0);
346+
}
347+
// Parent process just continues from here.
348+
349+
// Close unused pipes
350+
close(atos_stdin);
351+
close(atos_stdout);
352+
353+
auto to_atos = fdopen(write_to_atos, "w");
354+
auto from_atos = fdopen(read_from_atos, "r");
355+
356+
// buffer for atos reading. Note that symbol names can be super large...
357+
static char[16 * 1024] read_buffer = void;
358+
359+
char* status_ptr = null;
360+
foreach (ref loc; locations)
361+
{
362+
fprintf(to_atos, "%p\n", loc.address);
363+
fflush(to_atos);
364+
read_buffer[0] = '\0';
365+
status_ptr = fgets(read_buffer.ptr, read_buffer.sizeof, from_atos);
366+
if (!status_ptr)
367+
break;
368+
Location parsed_loc = parseAtosLine(read_buffer.ptr);
369+
if (parsed_loc.line != -1)
370+
{
371+
// Only update the file:line info, keep the procedure name as found before (preserving the standard truncation).
372+
loc.file = parsed_loc.file;
373+
loc.line = parsed_loc.line;
374+
}
375+
}
376+
377+
if (!status_ptr)
378+
printf("\nDid not succeed in using 'atos' for extra debug information.\n");
379+
380+
fclose(to_atos);
381+
fclose(from_atos);
382+
close(write_to_atos);
383+
close(read_from_atos);
384+
}
385+
386+
private Location parseAtosLine(char* buffer) @nogc nothrow
387+
{
388+
// The line from `atos` is in one of these formats:
389+
// myfunction (in library.dylib) (sourcefile.c:17)
390+
// myfunction (in library.dylib) + 0x1fe
391+
// myfunction (in library.dylib) + 15
392+
// 0xdeadbeef (in library.dylib) + 0x1fe
393+
// 0xdeadbeef (in library.dylib) + 15
394+
// 0xdeadbeef (in library.dylib)
395+
// 0xdeadbeef
396+
397+
import core.stdc.stdlib : atoi;
398+
import core.stdc.string : strchr, strstr;
399+
import core.sys.posix.string : strndup;
400+
401+
Location loc;
402+
403+
if (!buffer)
404+
return loc;
405+
if (buffer[0] == '0' && buffer[1] == 'x')
406+
// no named symbol found
407+
return loc;
408+
409+
const symbolname_end = strstr(buffer, " (in ");
410+
if (!symbolname_end)
411+
return loc;
412+
const symbolname_size = symbolname_end - buffer;
413+
loc.procedure = strndup(buffer, symbolname_size)[0..symbolname_size];
414+
415+
const filename_start = strstr(symbolname_end, ") (") + 3;
416+
if (cast(size_t)filename_start < 4)
417+
return loc;
418+
const colon_location = strchr(filename_start, ':');
419+
if (!colon_location)
420+
return loc;
421+
const filename_size = colon_location - filename_start;
422+
loc.file = strndup(filename_start, filename_size)[0..filename_size];
423+
424+
const final_paren = strchr(colon_location+1, ')');
425+
if (!final_paren)
426+
return loc;
427+
loc.line = atoi(colon_location+1);
428+
429+
return loc;
430+
}
431+
}
432+
271433
/**
272434
* Resolve the addresses of `locations` using `debugLineSectionData`
273435
*

runtime/druntime/test/exceptions/Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ $(ROOT)/line_trace.done: $(ROOT)/line_trace
3939
@echo Testing line_trace
4040
$(QUIET)$(TIMELIMIT)$(ROOT)/line_trace $(RUN_ARGS) > $(ROOT)/line_trace.output
4141
# Use sed to canonicalize line_trace.output and compare against expected output in line_trace.exp
42-
$(QUIET)$(SED) "s/\[0x[0-9a-f]*\]/\[ADDR\]/g; s/scope //g; s/Nl//g" $(ROOT)/line_trace.output | $(DIFF) line_trace.exp -
42+
$(QUIET)$(SED) "s|^.*/src/|src/|g; s/\[0x[0-9a-f]*\]/\[ADDR\]/g; s/scope //g; s/Nl//g" $(ROOT)/line_trace.output | $(DIFF) line_trace.exp -
4343
@rm -f $(ROOT)/line_trace.output
4444
@touch $@
4545

@@ -49,15 +49,15 @@ $(ROOT)/line_trace_21656.done: $(ROOT)/line_trace
4949
@mkdir -p $(ROOT)/line_trace_21656
5050
@touch $(ROOT)/line_trace_21656/line_trace
5151
$(QUIET)cd $(ROOT)/line_trace_21656 && PATH="..:$$PATH" $(TIMELIMIT)line_trace $(RUN_ARGS) > line_trace.output
52-
$(QUIET)$(SED) "s/\[0x[0-9a-f]*\]/\[ADDR\]/g; s/scope //g; s/Nl//g" $(ROOT)/line_trace_21656/line_trace.output | $(DIFF) line_trace.exp -
52+
$(QUIET)$(SED) "s|^.*/src/|src/|g; s/\[0x[0-9a-f]*\]/\[ADDR\]/g; s/scope //g; s/Nl//g" $(ROOT)/line_trace_21656/line_trace.output | $(DIFF) line_trace.exp -
5353
@rm -rf $(ROOT)/line_trace_21656
5454
@touch $@
5555

5656
$(ROOT)/long_backtrace_trunc.done: $(ROOT)/long_backtrace_trunc
5757
@echo Testing long_backtrace_trunc
5858
$(QUIET)$(TIMELIMIT)$(ROOT)/long_backtrace_trunc $(RUN_ARGS) > $(ROOT)/long_backtrace_trunc.output
5959
# Use sed to canonicalize long_backtrace_trunc.output and compare against expected output in long_backtrace_trunc.exp
60-
$(QUIET)$(SED) "s/\[0x[0-9a-f]*\]/\[ADDR\]/g; s/scope //g; s/Nl//g" $(ROOT)/long_backtrace_trunc.output | $(DIFF) long_backtrace_trunc.exp -
60+
$(QUIET)$(SED) "s|^.*/src/|src/|g; s/\[0x[0-9a-f]*\]/\[ADDR\]/g; s/scope //g; s/Nl//g; s|.F.*F.|.|g; s|.G.*G.|.|g" $(ROOT)/long_backtrace_trunc.output | $(DIFF) long_backtrace_trunc.exp -
6161
@rm -f $(ROOT)/long_backtrace_trunc.output
6262
@touch $@
6363

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
object.Exception@src/long_backtrace_trunc.d(10): test
22
----------------
3-
src/long_backtrace_trunc.d:10 pure @safe int long_backtrace_trunc.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!(int).AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC.DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD.EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE.FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG...
3+
src/long_backtrace_trunc.d:10 pure @safe int long_backtrace_trunc.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!(int).AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC.DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD.EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE...
44
src/long_backtrace_trunc.d:31 _Dmain [ADDR]

0 commit comments

Comments
 (0)