|
|
Message-ID: <ef05ab74-3718-483f-a5a7-3a45e0e5b45a@jdownloader.org>
Date: Sat, 1 Aug 2026 17:22:07 +0200
From: Lukas Gottschall <dwd@...wnloader.org>
To: musl@...ts.openwall.com
Subject: [PATCH] add-cfi generators: make sleeping threads debuggable on arm,
aarch64, riscv32, riscv64, powerpc64, s390x and loongarch64
musl builds the unwind information for its assembly with
tools/add-cfi.$ARCH.awk.
Those scripts exist for i386 and x86_64 only; everywhere else there is none.
On platforms like ARM this means sleeping and waiting threads
can no longer be debugged - the backtrace stops inside libc.
It hits every waiting thread, because every blocking call goes through
__syscall_cp_asm.
Suggested here in 2024:
"if you provide that for another target that will work too" [1].
A 2020 patch wrote the directives into the assembly instead [2].
The awk files fix this on 7 of the 16 affected architectures.
Please note that I did not write an awk file for every architecture:
I have no test environment for the rest.
No source file is changed - configure finds the awk files
on its own and only uses them when the build has -g.
The machine code is unchanged, byte for byte.
Or without applying the patches:
for a in arm aarch64 riscv32 riscv64 powerpc64 s390x loongarch64; do
wget -P tools
https://raw.githubusercontent.com/lgoio/MuslBugReport/refs/heads/main/patch/tools/add-cfi.$a.awk
done
Each was checked by building musl with it,
on arm and aarch64 down to a backtrace with plain gdb.
Written by Claude (Anthropic's coding assistant), reviewed by hand,
checked automatically under Docker.
For existing musl systems on ARM (ARMv7 & aarch64)
there is this gdb workaround, which needs no change to the library:
https://raw.githubusercontent.com/lgoio/MuslBugReport/refs/heads/main/container/gdb_musl_unwinder.py
(gdb) attach PID
(gdb) source gdb_musl_unwinder.py # after attaching, not before
(gdb) set backtrace limit 30 # __clone repeats without it
(gdb) thread apply all -c bt # -c, or it aborts at the first
thread
The test setup for the Alpine architectures and the full output
can be found in the git repository:
https://github.com/lgoio/MuslBugReport
Regards,
Lukas Gottschall
[1] https://www.openwall.com/lists/musl/2024/03/13/6
[2] https://www.openwall.com/lists/musl/2020/07/05/1
========================================================================
arm: add tools/add-cfi.arm.awk
========================================================================
__syscall_cp_asm pushes four registers, so the frame needs an adjusted
CFA and
a rel_offset each. __cp_end pops them while __cp_cancel is branched to with
them still saved, so the pop is bracketed with .cfi_remember_state /
.cfi_restore_state; a return ends a path, so no flow analysis is needed.
clone.s zeroes the frame pointer in the child; the generator repeats that in
CFI with .cfi_undefined for the return address, or __clone repeats to the
backtrace limit. This is where the gap shows - gdb's fallback assumes
CFA = sp
and loses the application frames. Verified down to a backtrace with
plain gdb.
---
--- /dev/null
+++ b/tools/add-cfi.arm.awk
@@ -0,0 +1,240 @@
+# Insert GAS CFI directives ("control frame information") into 32-bit
ARM asm input
+
+BEGIN {
+ # don't put CFI data in the .eh_frame ELF section (which we don't keep)
+ print ".cfi_sections .debug_frame"
+
+ # only emit CFI directives inside a function
+ in_function = 0
+
+ # emit .loc directives with line numbers from original source
+ printf ".file 1 \"%s\"\n", ARGV[1]
+ line_number = 0
+
+ # set between a stack restore and the return that follows it, so the two
+ # halves of .cfi_remember_state/.cfi_restore_state always pair up
+ remembered = 0
+}
+
+function adjust_sp_offset(delta) {
+ if (in_function)
+ printf ".cfi_adjust_cfa_offset %d\n", delta
+}
+
+# r0-r15 by number, so a register list can be sorted into transfer order
+function regnum(register) {
+ if (register == "sl") return 10
+ if (register == "fp") return 11
+ if (register == "ip") return 12
+ if (register == "sp") return 13
+ if (register == "lr") return 14
+ if (register == "pc") return 15
+ sub(/^r/, "", register)
+ return register+0
+}
+
+# {r4,r5,r6,r7} and {r4-r7} both mean the same four registers. Fills regs[]
+# with their numbers in ascending order and returns how many there are
- stm
+# and ldm always transfer in register order, whatever order they are
written.
+function reglist(str, i, j, n, m, items, range, out, count, tmp) {
+ match(str, /\{[^}]*\}/)
+ str = substr(str, RSTART+1, RLENGTH-2)
+ n = split(str, items, ",")
+ count = 0
+ for (i = 1; i <= n; i++) {
+ if (index(items[i], "-")) {
+ split(items[i], range, "-")
+ for (j = regnum(range[1]); j <= regnum(range[2]); j++)
+ out[++count] = j
+ } else {
+ out[++count] = regnum(items[i])
+ }
+ }
+ for (i = 2; i <= count; i++) { # insertion sort, lists are tiny
+ tmp = out[i]
+ for (j = i-1; j >= 1 && out[j] > tmp; j--)
+ out[j+1] = out[j]
+ out[j+1] = tmp
+ }
+ for (i = 1; i <= count; i++)
+ regs[i] = out[i]
+ return count
+}
+
+# The generator's own idea of which registers still hold the caller's
value has
+# to follow .cfi_remember_state/.cfi_restore_state, or the state it
assumes and
+# the state the assembler assumes drift apart after the first return path.
+function snapshot( register) {
+ for (register in saved_at_remember) delete saved_at_remember[register]
+ for (register in dirty_at_remember) delete dirty_at_remember[register]
+ for (register in saved) saved_at_remember[register] = 1
+ for (register in dirty) dirty_at_remember[register] = 1
+}
+function rollback( register) {
+ for (register in saved) delete saved[register]
+ for (register in dirty) delete dirty[register]
+ for (register in saved_at_remember) saved[register] = 1
+ for (register in dirty_at_remember) dirty[register] = 1
+}
+
+{
+ line_number = line_number + 1
+
+
+ # clean the input up before doing anything else
+ # delete comments
+ gsub(/(@|\/\/).*/, "")
+
+ # canonicalize whitespace
+ gsub(/[ \t]+/, " ") # mawk doesn't understand \s
+ gsub(/ *, */, ",")
+ gsub(/ *: */, ": ")
+ gsub(/ $/, "")
+ gsub(/^ /, "")
+
+ # A label may share the line with the instruction it marks - clone.s has
+ # "1: mov fp,#0". The rules below match on insn instead of $0, so
they see
+ # that instruction too, while the line is still printed with its label.
+ insn = $0
+ sub(/^[a-zA-Z0-9_]+: /, "", insn)
+}
+
+# check for assembler directives which we care about
+/^\.(section|data|text)/ {
+ # a .cfi_startproc/.cfi_endproc pair should be within the same section
+ # otherwise, clang will choke when generating ELF output
+ if (in_function) {
+ print ".cfi_endproc"
+ in_function = 0
+ remembered = 0
+ }
+}
+/^\.type [a-zA-Z0-9_]+,%function/ {
+ functions[substr($2, 1, length($2)-10)] = 1
+}
+# not interested in assembler directives beyond this, just pass them
through
+/^\./ {
+ print
+ next
+}
+
+/^[a-zA-Z0-9_]+:/ {
+ label = substr($1, 1, length($1)-1) # drop trailing :
+
+ if (functions[label]) {
+ if (in_function)
+ print ".cfi_endproc"
+
+ in_function = 1
+ remembered = 0
+ print ".cfi_startproc"
+
+ for (register in saved)
+ delete saved[register]
+ for (register in dirty)
+ delete dirty[register]
+ }
+
+ # an instruction may follow on the same line, so continue processing
+}
+
+
+/^$/ { next }
+
+{
+ printf ".loc 1 %d\n", line_number
+ print
+}
+
+# KEEPING UP WITH THE STACK POINTER
+# sp is only ever adjusted by a push or a pop in this source tree;
there is no
+# "sub sp,sp,#n" anywhere, so anything else touching sp is left alone on
+# purpose rather than guessed at.
+#
+insn ~ /^(push \{|stmfd sp!,\{|stmdb sp!,\{)/ {
+ if (in_function) {
+ count = reglist(insn)
+ adjust_sp_offset(4 * count)
+
+ # A pushed register keeps the caller's value if nothing has
overwritten it
+ # yet, and then the copy on the stack is the one to report one
level up.
+ for (i = 1; i <= count; i++) {
+ if (!saved[regs[i]] && !dirty[regs[i]]) {
+ printf ".cfi_rel_offset r%d,%d\n", regs[i], 4 * (i-1)
+ saved[regs[i]] = 1
+ }
+ }
+ }
+}
+
+insn ~ /^(pop \{|ldmfd sp!,\{|ldmia sp!,\{)/ {
+ if (in_function) {
+ # The pop belongs to one return path, but the instructions after it may
+ # belong to another that never popped - __cp_cancel in syscall_cp.s
and the
+ # child half of clone.s are both reached with the registers still
saved.
+ # Bracketing the restore keeps those paths on the pushed state
instead of
+ # inheriting this one.
+ if (!remembered) {
+ print ".cfi_remember_state"
+ snapshot()
+ remembered = 1
+ }
+ count = reglist(insn)
+ adjust_sp_offset(-4 * count)
+ for (i = 1; i <= count; i++) {
+ if (saved[regs[i]]) {
+ printf ".cfi_restore r%d\n", regs[i]
+ delete saved[regs[i]]
+ }
+ }
+ }
+}
+
+# End of a path: a return, or a tail call that never comes back.
Conditional
+# branches are deliberately not matched - they do not end anything.
+insn ~ /^(bx lr|bx r14|mov pc,lr|b [a-zA-Z0-9_]+|b [0-9]+[bf])$/ {
+ if (in_function && remembered) {
+ print ".cfi_restore_state"
+ rollback()
+ remembered = 0
+ }
+}
+
+# IF REGISTER VALUES ARE UNCEREMONIOUSLY TRASHED
+# ...then we want to know about it. Not an exhaustive list of
instructions that
+# can overwrite an inherited register, just the ones this source tree uses.
+function trashed(register, n) {
+ n = regnum(register)
+ if (in_function && !saved[n] && !dirty[n])
+ printf ".cfi_undefined r%d\n", n
+ dirty[n] = 1
+}
+insn ~ /^(mov|mvn|add|sub|rsb|and|orr|eor|bic|lsl|lsr|asr|mul|ldr)
[a-z0-9]+,/ {
+ if (in_function)
+ trashed(substr(insn, index(insn, " ")+1, index(insn,
",")-index(insn, " ")-1))
+}
+# ldm without a "!" writes registers without touching sp
+insn ~ /^ldm(fd|ia|db|ea)? [a-z0-9]+,\{/ {
+ if (in_function) {
+ count = reglist(insn)
+ for (i = 1; i <= count; i++)
+ trashed("r" regs[i])
+ }
+}
+
+# END OF A THREAD STACK
+# Zeroing the frame pointer is musl's own marker for it: clone.s does
that in
+# the child so frame-pointer unwinders stop there. Say the same in CFI by
+# marking the return address undefined - that is the register a debugger
+# follows - otherwise it keeps going into whatever the registers happen to
+# hold and reports __clone over and over.
+insn ~ /^mov fp,#?0$/ {
+ if (in_function)
+ print ".cfi_undefined r14"
+}
+
+
+END {
+ if (in_function)
+ print ".cfi_endproc"
+}
========================================================================
aarch64: add tools/add-cfi.aarch64.awk
========================================================================
syscall_cp.s has no prologue - __syscall_cp_asm and __cp_begin share an
address
and the stub returns through x30 - so the default rules describe it and
.cfi_startproc/.cfi_endproc are enough for the missing FDE.
Pre-indexed stp and post-indexed ldp are the only way sp moves here; unlike
ARM's stm, stp stores its operands in the order written. clone.s saves
to the
new thread stack in x1, not sp, and its child zeroes the frame pointer,
which
becomes .cfi_undefined for x30. Verified down to a backtrace with plain gdb.
---
--- /dev/null
+++ b/tools/add-cfi.aarch64.awk
@@ -0,0 +1,201 @@
+# Insert GAS CFI directives ("control frame information") into aarch64
asm input
+
+BEGIN {
+ # don't put CFI data in the .eh_frame ELF section (which we don't keep)
+ print ".cfi_sections .debug_frame"
+
+ # only emit CFI directives inside a function
+ in_function = 0
+
+ # emit .loc directives with line numbers from original source
+ printf ".file 1 \"%s\"\n", ARGV[1]
+ line_number = 0
+
+ # set between a stack restore and the return that follows it, so the two
+ # halves of .cfi_remember_state/.cfi_restore_state always pair up
+ remembered = 0
+}
+
+function adjust_sp_offset(delta) {
+ if (in_function)
+ printf ".cfi_adjust_cfa_offset %d\n", delta
+}
+
+# The generator's own idea of which registers still hold the caller's
value has
+# to follow .cfi_remember_state/.cfi_restore_state, or the state it
assumes and
+# the state the assembler assumes drift apart after the first return path.
+function snapshot( register) {
+ for (register in saved_at_remember) delete saved_at_remember[register]
+ for (register in dirty_at_remember) delete dirty_at_remember[register]
+ for (register in saved) saved_at_remember[register] = 1
+ for (register in dirty) dirty_at_remember[register] = 1
+}
+function rollback( register) {
+ for (register in saved) delete saved[register]
+ for (register in dirty) delete dirty[register]
+ for (register in saved_at_remember) saved[register] = 1
+ for (register in dirty_at_remember) dirty[register] = 1
+}
+
+{
+ line_number = line_number + 1
+
+
+ # clean the input up before doing anything else
+ # delete comments
+ gsub(/(\/\/).*/, "")
+
+ # canonicalize whitespace
+ gsub(/[ \t]+/, " ") # mawk doesn't understand \s
+ gsub(/ *, */, ",")
+ gsub(/ *: */, ": ")
+ gsub(/ $/, "")
+ gsub(/^ /, "")
+
+ # A label may share the line with the instruction it marks - clone.s has
+ # "1: mov x29, 0". The rules below match on insn instead of $0, so
they see
+ # that instruction too, while the line is still printed with its label.
+ insn = $0
+ sub(/^[a-zA-Z0-9_]+: /, "", insn)
+}
+
+# check for assembler directives which we care about
+/^\.(section|data|text)/ {
+ # a .cfi_startproc/.cfi_endproc pair should be within the same section
+ # otherwise, clang will choke when generating ELF output
+ if (in_function) {
+ print ".cfi_endproc"
+ in_function = 0
+ remembered = 0
+ }
+}
+/^\.type [a-zA-Z0-9_]+,%function/ {
+ functions[substr($2, 1, length($2)-10)] = 1
+}
+# not interested in assembler directives beyond this, just pass them
through
+/^\./ {
+ print
+ next
+}
+
+/^[a-zA-Z0-9_]+:/ {
+ label = substr($1, 1, length($1)-1) # drop trailing :
+
+ if (functions[label]) {
+ if (in_function)
+ print ".cfi_endproc"
+
+ in_function = 1
+ remembered = 0
+ print ".cfi_startproc"
+
+ for (register in saved)
+ delete saved[register]
+ for (register in dirty)
+ delete dirty[register]
+ }
+
+ # an instruction may follow on the same line, so continue processing
+}
+
+
+/^$/ { next }
+
+{
+ printf ".loc 1 %d\n", line_number
+ print
+}
+
+# KEEPING UP WITH THE STACK POINTER
+# sp is only ever adjusted by a pre-indexed stp or a post-indexed ldp
in this
+# source tree; there is no "sub sp,sp,#n" anywhere, so anything else
touching
+# sp is left alone on purpose rather than guessed at. crti.s is what
the store
+# rule is for; clone.s saves to the new thread stack in x1, not to sp.
+#
+# Unlike ARM's stm, stp stores its operands in the order they are written:
+# "stp xA,xB,[sp,#-16]!" puts xA at the new sp and xB eight bytes above it.
+#
+insn ~ /^stp x[0-9]+,x[0-9]+,\[sp,#?-[0-9]+\]!$/ {
+ if (in_function) {
+ split(insn, part, ",")
+ first = part[1]; sub(/^stp /, "", first)
+ second = part[2]
+ size = part[3]; gsub(/[^0-9]/, "", size)
+ adjust_sp_offset(size + 0)
+
+ # A stored register keeps the caller's value if nothing has
overwritten it
+ # yet, and then the copy on the stack is the one to report one
level up.
+ if (!saved[first] && !dirty[first]) {
+ printf ".cfi_rel_offset %s,0\n", first
+ saved[first] = 1
+ }
+ if (!saved[second] && !dirty[second]) {
+ printf ".cfi_rel_offset %s,8\n", second
+ saved[second] = 1
+ }
+ }
+}
+
+insn ~ /^ldp x[0-9]+,x[0-9]+,\[sp\],#?[0-9]+$/ {
+ if (in_function) {
+ # The load belongs to one return path, but the instructions after
it may
+ # belong to another that never loaded anything. Bracketing the restore
+ # keeps that path on the stored state instead of inheriting this
one, and
+ # it needs no flow analysis, since a return ends a path. The only
load from
+ # sp in this tree is the one in the child of clone.s, which runs
after the
+ # end-of-stack marker below has already stopped the unwinder.
+ if (!remembered) {
+ print ".cfi_remember_state"
+ snapshot()
+ remembered = 1
+ }
+ split(insn, part, ",")
+ first = part[1]; sub(/^ldp /, "", first)
+ second = part[2]
+ size = part[3]; gsub(/[^0-9]/, "", size)
+ adjust_sp_offset(-(size + 0))
+ if (saved[first]) { printf ".cfi_restore %s\n", first; delete
saved[first] }
+ if (saved[second]) { printf ".cfi_restore %s\n", second; delete
saved[second] }
+ }
+}
+
+# End of a path: a return, or a tail call that never comes back.
Conditional
+# branches - cbz, cbnz, b.eq and friends - are deliberately not
matched, they
+# do not end anything.
+insn ~ /^(ret|ret x30|br x[0-9]+|b [a-zA-Z0-9_]+|b [0-9]+[bf])$/ {
+ if (in_function && remembered) {
+ print ".cfi_restore_state"
+ rollback()
+ remembered = 0
+ }
+}
+
+# IF REGISTER VALUES ARE UNCEREMONIOUSLY TRASHED
+# ...then we want to know about it. Not an exhaustive list of
instructions that
+# can overwrite an inherited register, just the ones this source tree uses.
+function trashed(register) {
+ if (in_function && !saved[register] && !dirty[register])
+ printf ".cfi_undefined %s\n", register
+ dirty[register] = 1
+}
+insn ~
/^(mov|mvn|add|sub|and|orr|eor|bic|lsl|lsr|asr|mul|ldr|uxtw|sxtw)
x[0-9]+,/ {
+ if (in_function)
+ trashed(substr(insn, index(insn, " ")+1, index(insn,
",")-index(insn, " ")-1))
+}
+
+# END OF A THREAD STACK
+# Zeroing the frame pointer is musl's own marker for it: clone.s does
that in
+# the child so frame-pointer unwinders stop there. Say the same in CFI by
+# marking the return address undefined - that is the register a debugger
+# follows - otherwise it keeps going into whatever the registers happen to
+# hold and reports __clone over and over.
+insn ~ /^mov x29,#?0$/ {
+ if (in_function)
+ print ".cfi_undefined x30"
+}
+
+
+END {
+ if (in_function)
+ print ".cfi_endproc"
+}
========================================================================
riscv64: add tools/add-cfi.riscv64.awk
========================================================================
Neither syscall_cp.s nor clone.s moves the stack pointer, so the default
rules
describe both and the generator only opens and closes the frame. "addi
sp,sp,-n"
and stores to the stack are handled for the files that do use them,
ldso/riscv64/tlsdesc.s today.
clone.s carries no end-of-stack marker and needs none: __clone appears
once per
thread. Clearing the return address the way _start does would not work,
because
the child calls the thread function with jalr, which sets it again.
Verified by reading the FDE coverage out of the built libc.so; no live
backtrace was taken here.
---
--- /dev/null
+++ b/tools/add-cfi.riscv64.awk
@@ -0,0 +1,125 @@
+# Insert GAS CFI directives ("control frame information") into riscv64
asm input
+#
+# Most of the riscv64 asm never moves the stack pointer - syscall_cp.s and
+# clone.s among them - so the default rules already describe those
frames and
+# the generator only has to open and close them. The two rules at the
bottom
+# are for the files that do, ldso/riscv64/tlsdesc.s today.
+
+BEGIN {
+ # don't put CFI data in the .eh_frame ELF section (which we don't keep)
+ print ".cfi_sections .debug_frame"
+
+ # only emit CFI directives inside a function
+ in_function = 0
+
+ # emit .loc directives with line numbers from original source
+ printf ".file 1 \"%s\"\n", ARGV[1]
+ line_number = 0
+}
+
+function adjust_sp_offset(delta) {
+ if (in_function)
+ printf ".cfi_adjust_cfa_offset %d\n", delta
+}
+
+{
+ line_number = line_number + 1
+
+
+ # clean the input up before doing anything else
+ # delete comments
+ gsub(/(#).*/, "")
+
+ # canonicalize whitespace
+ gsub(/[ \t]+/, " ") # mawk doesn't understand \s
+ gsub(/ *, */, ",")
+ gsub(/ *: */, ": ")
+ gsub(/ $/, "")
+ gsub(/^ /, "")
+
+ # A label may share the line with the instruction it marks. The rules
below
+ # match on this, so they do not have to care either way, while the line
+ # itself is still printed with its label intact.
+ insn = $0
+ sub(/^[a-zA-Z0-9_]+: /, "", insn)
+}
+
+# check for assembler directives which we care about
+/^\.(section|data|text)/ {
+ # a .cfi_startproc/.cfi_endproc pair should be within the same section
+ # otherwise, clang will choke when generating ELF output
+ if (in_function) {
+ print ".cfi_endproc"
+ in_function = 0
+ }
+}
+/^\.type [a-zA-Z0-9_]+,%function/ {
+ functions[substr($2, 1, length($2)-10)] = 1
+}
+# not interested in assembler directives beyond this, just pass them
through
+/^\./ {
+ print
+ next
+}
+
+/^[a-zA-Z0-9_]+:/ {
+ label = substr($1, 1, length($1)-1) # drop trailing :
+
+ if (functions[label]) {
+ if (in_function)
+ print ".cfi_endproc"
+
+ in_function = 1
+ print ".cfi_startproc"
+
+ for (register in saved)
+ delete saved[register]
+ }
+
+ # an instruction may follow on the same line, so continue processing
+}
+
+
+/^$/ { next }
+
+{
+ printf ".loc 1 %d\n", line_number
+ print
+}
+
+# KEEPING UP WITH THE STACK POINTER
+# "add sp,sp,-n" opens a frame and "add sp,sp,n" closes it again - the
operand
+# carries the sign, and the CFA offset moves the other way. Nothing else in
+# this source tree writes sp.
+#
+insn ~ /^addi? sp,sp,-?[0-9]+$/ {
+ if (in_function) {
+ n = insn
+ sub(/^addi? sp,sp,/, "", n)
+ adjust_sp_offset(-(n + 0))
+ }
+}
+
+# TRACKING REGISTER VALUES FROM THE PREVIOUS STACK FRAME
+# "sd ra,n(sp)" and friends put a register from the caller on the stack. A
+# store written without an offset - "sd t1,(sp)" - is passed over
rather than
+# read as 0: leaving a register untracked costs a value in the
debugger, while
+# guessing at the address would print the wrong one.
+#
+insn ~ /^(sd|sw) [a-z0-9]+,[0-9]+\(sp\)$/ {
+ if (in_function) {
+ register = substr(insn, index(insn, " ")+1, index(insn,
",")-index(insn, " ")-1)
+ offset = substr(insn, index(insn, ",")+1)
+ sub(/\(sp\)$/, "", offset)
+ if (!saved[register]) {
+ printf ".cfi_rel_offset %s,%d\n", register, offset+0
+ saved[register] = 1
+ }
+ }
+}
+
+
+END {
+ if (in_function)
+ print ".cfi_endproc"
+}
========================================================================
powerpc64: add tools/add-cfi.powerpc64.awk
========================================================================
Neither syscall_cp.s nor clone.s moves r1 - clone.s builds the child's
frame in
r4 - so the default rules describe both and the generator only opens and
closes
the frame. "stdu 1,-n(1)" and "addi 1,1,n" are handled anyway, so a file
that
does move r1 is not silently mistracked; crti.s and crtn.s use them but
declare
no .type,%function and emit nothing today.
clone.s carries no end-of-stack marker and needs none: __clone appears
once per
thread. Verified by reading the FDE coverage out of the built libc.so;
no live
backtrace was taken here.
---
--- /dev/null
+++ b/tools/add-cfi.powerpc64.awk
@@ -0,0 +1,117 @@
+# Insert GAS CFI directives ("control frame information") into
powerpc64 asm input
+#
+# The powerpc64 asm this runs over never moves the stack pointer inside a
+# function that declares .type,%function - syscall_cp.s and clone.s
among them
+# - so the default rules already describe those frames and the
generator only
+# has to open and close them. The two rules at the bottom are for the files
+# that do.
+
+BEGIN {
+ # don't put CFI data in the .eh_frame ELF section (which we don't keep)
+ print ".cfi_sections .debug_frame"
+
+ # only emit CFI directives inside a function
+ in_function = 0
+
+ # emit .loc directives with line numbers from original source
+ printf ".file 1 \"%s\"\n", ARGV[1]
+ line_number = 0
+}
+
+function adjust_sp_offset(delta) {
+ if (in_function)
+ printf ".cfi_adjust_cfa_offset %d\n", delta
+}
+
+{
+ line_number = line_number + 1
+
+
+ # clean the input up before doing anything else
+ # delete comments
+ gsub(/(#).*/, "")
+
+ # canonicalize whitespace
+ gsub(/[ \t]+/, " ") # mawk doesn't understand \s
+ gsub(/ *, */, ",")
+ gsub(/ *: */, ": ")
+ gsub(/ $/, "")
+ gsub(/^ /, "")
+
+ # A label may share the line with the instruction it marks. The rules
below
+ # match on this, so they do not have to care either way, while the line
+ # itself is still printed with its label intact.
+ insn = $0
+ sub(/^[a-zA-Z0-9_]+: /, "", insn)
+}
+
+# check for assembler directives which we care about
+/^\.(section|data|text)/ {
+ # a .cfi_startproc/.cfi_endproc pair should be within the same section
+ # otherwise, clang will choke when generating ELF output
+ if (in_function) {
+ print ".cfi_endproc"
+ in_function = 0
+ }
+}
+/^\.type [a-zA-Z0-9_]+,%function/ {
+ functions[substr($2, 1, length($2)-10)] = 1
+}
+# not interested in assembler directives beyond this, just pass them
through
+/^\./ {
+ print
+ next
+}
+
+/^[a-zA-Z0-9_]+:/ {
+ label = substr($1, 1, length($1)-1) # drop trailing :
+
+ if (functions[label]) {
+ if (in_function)
+ print ".cfi_endproc"
+
+ in_function = 1
+ print ".cfi_startproc"
+
+ for (register in saved)
+ delete saved[register]
+ }
+
+ # an instruction may follow on the same line, so continue processing
+}
+
+
+/^$/ { next }
+
+{
+ printf ".loc 1 %d\n", line_number
+ print
+}
+
+# KEEPING UP WITH THE STACK POINTER
+# r1 is the stack pointer. "stdu 1,-n(1)" pushes a frame; "addi 1,1,n"
pops it.
+# Both appear in crti.s and crtn.s, which declare no .type,%function and so
+# emit nothing today - the rules are here so a file that does declare
one is
+# not silently mistracked. clone.s writes the new thread stack through
r4, not
+# r1, and is not matched.
+#
+insn ~ /^stdu 1,-[0-9]+\(1\)$/ {
+ if (in_function) {
+ n = insn
+ sub(/^stdu 1,-/, "", n); sub(/\(1\)$/, "", n)
+ adjust_sp_offset(n + 0)
+ }
+}
+insn ~ /^addi 1,1,[0-9]+$/ {
+ if (in_function) {
+ n = insn
+ sub(/^addi 1,1,/, "", n)
+ adjust_sp_offset(-(n + 0))
+ }
+}
+
+
+END {
+ if (in_function)
+ print ".cfi_endproc"
+}
========================================================================
s390x: add tools/add-cfi.s390x.awk
========================================================================
The stack pointer does not move in either file. syscall_cp.s saves r6 and r7
into the caller-provided register save area, which sits above the CFA, hence
the positive rel_offset. "aghi %r15,-n" is handled anyway, so a file
that does
open a frame is not silently mistracked; crti.s uses it but declares no
.type,%function and emits nothing today.
clone.s carries no end-of-stack marker and needs none: __clone appears
once per
thread. Verified by reading the FDE coverage out of the built libc.so;
no live
backtrace was taken here.
---
--- /dev/null
+++ b/tools/add-cfi.s390x.awk
@@ -0,0 +1,122 @@
+# Insert GAS CFI directives ("control frame information") into s390x
asm input
+
+BEGIN {
+ # don't put CFI data in the .eh_frame ELF section (which we don't keep)
+ print ".cfi_sections .debug_frame"
+
+ # only emit CFI directives inside a function
+ in_function = 0
+
+ # emit .loc directives with line numbers from original source
+ printf ".file 1 \"%s\"\n", ARGV[1]
+ line_number = 0
+}
+
+function adjust_sp_offset(delta) {
+ if (in_function)
+ printf ".cfi_adjust_cfa_offset %d\n", delta
+}
+
+{
+ line_number = line_number + 1
+
+
+ # clean the input up before doing anything else
+ # delete comments
+ gsub(/(#).*/, "")
+
+ # canonicalize whitespace
+ gsub(/[ \t]+/, " ") # mawk doesn't understand \s
+ gsub(/ *, */, ",")
+ gsub(/ *: */, ": ")
+ gsub(/ $/, "")
+ gsub(/^ /, "")
+
+ # A label may share the line with the instruction it marks. The rules
below
+ # match on this, so they do not have to care either way, while the line
+ # itself is still printed with its label intact.
+ insn = $0
+ sub(/^[a-zA-Z0-9_]+: /, "", insn)
+}
+
+# check for assembler directives which we care about
+/^\.(section|data|text)/ {
+ # a .cfi_startproc/.cfi_endproc pair should be within the same section
+ # otherwise, clang will choke when generating ELF output
+ if (in_function) {
+ print ".cfi_endproc"
+ in_function = 0
+ }
+}
+/^\.type [a-zA-Z0-9_]+,%function/ {
+ functions[substr($2, 1, length($2)-10)] = 1
+}
+# not interested in assembler directives beyond this, just pass them
through
+/^\./ {
+ print
+ next
+}
+
+/^[a-zA-Z0-9_]+:/ {
+ label = substr($1, 1, length($1)-1) # drop trailing :
+
+ if (functions[label]) {
+ if (in_function)
+ print ".cfi_endproc"
+
+ in_function = 1
+ print ".cfi_startproc"
+
+ for (register in saved)
+ delete saved[register]
+ }
+
+ # an instruction may follow on the same line, so continue processing
+}
+
+
+/^$/ { next }
+
+{
+ printf ".loc 1 %d\n", line_number
+ print
+}
+
+# KEEPING UP WITH THE STACK POINTER
+# %r15 is the stack pointer and "aghi %r15,-n" is the only form that
opens a
+# frame here. It appears in crti.s, which declares no .type,%function
and so
+# emits nothing today - the rule is here so a file that does declare one is
+# not silently mistracked. clone.s builds the new thread stack in %r3
and is
+# not matched.
+#
+insn ~ /^aghi %r15,-[0-9]+$/ {
+ if (in_function) {
+ n = insn
+ sub(/^aghi %r15,-/, "", n)
+ adjust_sp_offset(n + 0)
+ }
+}
+
+# TRACKING REGISTER VALUES FROM THE PREVIOUS STACK FRAME
+# The caller provides the register save area, so a "stg %rN,off(%r15)"
stores
+# above the CFA rather than below it - hence the positive offset. This
is the
+# rule that carries the frame here: syscall_cp.s saves %r6 and %r7 that
way,
+# clone.s saves %r6.
+#
+insn ~ /^stg %r[0-9]+,[0-9]+\(%r15\)$/ {
+ if (in_function) {
+ register = substr(insn, index(insn, " ")+1, index(insn,
",")-index(insn, " ")-1)
+ offset = substr(insn, index(insn, ",")+1)
+ sub(/\(%r15\)$/, "", offset)
+ if (!saved[register]) {
+ printf ".cfi_rel_offset %s,%d\n", register, offset+0
+ saved[register] = 1
+ }
+ }
+}
+
+
+END {
+ if (in_function)
+ print ".cfi_endproc"
+}
========================================================================
loongarch64: add tools/add-cfi.loongarch64.awk
========================================================================
syscall_cp.s has no prologue and returns through ra, so opening and
closing the
frame is enough. clone.s zeroes the frame pointer in the child, which
becomes
.cfi_undefined for ra.
Note this port spells the type directive .type name,@function rather than
%function, like x86 and unlike the other ports here.
Verified by reading the FDE coverage out of the built libc.so; no live
backtrace was taken here.
---
--- /dev/null
+++ b/tools/add-cfi.loongarch64.awk
@@ -0,0 +1,132 @@
+# Insert GAS CFI directives ("control frame information") into
loongarch64 asm input
+#
+# The loongarch64 asm never moves the stack pointer - syscall_cp.s and
clone.s
+# among them - so the default rules already describe those frames and the
+# generator only has to open and close them, plus mark the end of a thread
+# stack. The two rules in the middle are for the files that do.
+
+BEGIN {
+ # don't put CFI data in the .eh_frame ELF section (which we don't keep)
+ print ".cfi_sections .debug_frame"
+
+ # only emit CFI directives inside a function
+ in_function = 0
+
+ # emit .loc directives with line numbers from original source
+ printf ".file 1 \"%s\"\n", ARGV[1]
+ line_number = 0
+}
+
+function adjust_sp_offset(delta) {
+ if (in_function)
+ printf ".cfi_adjust_cfa_offset %d\n", delta
+}
+
+{
+ line_number = line_number + 1
+
+
+ # clean the input up before doing anything else
+ # delete comments
+ gsub(/(#).*/, "")
+
+ # canonicalize whitespace
+ gsub(/[ \t]+/, " ") # mawk doesn't understand \s
+ gsub(/ *, */, ",")
+ gsub(/ *: */, ": ")
+ gsub(/ $/, "")
+ gsub(/^ /, "")
+
+ # A label may share the line with the instruction it marks. The rules
below
+ # match on this, so they do not have to care either way, while the line
+ # itself is still printed with its label intact.
+ insn = $0
+ sub(/^[a-zA-Z0-9_]+: /, "", insn)
+}
+
+# check for assembler directives which we care about
+/^\.(section|data|text)/ {
+ # a .cfi_startproc/.cfi_endproc pair should be within the same section
+ # otherwise, clang will choke when generating ELF output
+ if (in_function) {
+ print ".cfi_endproc"
+ in_function = 0
+ }
+}
+/^\.type [a-zA-Z0-9_]+,@function/ {
+ functions[substr($2, 1, length($2)-10)] = 1
+}
+# not interested in assembler directives beyond this, just pass them
through
+/^\./ {
+ print
+ next
+}
+
+/^[a-zA-Z0-9_]+:/ {
+ label = substr($1, 1, length($1)-1) # drop trailing :
+
+ if (functions[label]) {
+ if (in_function)
+ print ".cfi_endproc"
+
+ in_function = 1
+ print ".cfi_startproc"
+
+ for (register in saved)
+ delete saved[register]
+ }
+
+ # an instruction may follow on the same line, so continue processing
+}
+
+
+/^$/ { next }
+
+{
+ printf ".loc 1 %d\n", line_number
+ print
+}
+
+# KEEPING UP WITH THE STACK POINTER
+# "addi.d $sp,$sp,-n" is the only form that opens a frame here - the
operand
+# carries the sign, and the CFA offset moves the other way. clone.s
builds the
+# new thread stack in $a1 and is not matched.
+#
+insn ~ /^addi\.d \$sp,\$sp,-?[0-9]+$/ {
+ if (in_function) {
+ n = insn
+ sub(/^addi\.d \$sp,\$sp,/, "", n)
+ adjust_sp_offset(-(n + 0))
+ }
+}
+
+# TRACKING REGISTER VALUES FROM THE PREVIOUS STACK FRAME
+# "st.d $ra,$sp,n" puts a register from the caller on the stack.
+#
+insn ~ /^st\.[dw] \$[a-z0-9]+,\$sp,[0-9]+$/ {
+ if (in_function) {
+ split(insn, part, ",")
+ register = part[1]; sub(/^st\.[dw] /, "", register)
+ if (!saved[register]) {
+ printf ".cfi_rel_offset %s,%d\n", register, part[3]+0
+ saved[register] = 1
+ }
+ }
+}
+
+# END OF A THREAD STACK
+# Zeroing the frame pointer is musl's own marker for it: clone.s does
that in
+# the child so frame-pointer unwinders stop there. Say the same in CFI by
+# marking the return address undefined - that is the register a debugger
+# follows - otherwise it keeps going into whatever the registers happen to
+# hold and reports __clone over and over.
+insn ~ /^move \$fp,\$zero$/ {
+ if (in_function)
+ print ".cfi_undefined $ra"
+}
+
+
+END {
+ if (in_function)
+ print ".cfi_endproc"
+}
========================================================================
riscv32: add tools/add-cfi.riscv32.awk
========================================================================
riscv32 differs from riscv64 in these two files only in the load and store
widths, and neither moves the stack pointer: open and close the frame,
let the
default rules describe it. The generator is the riscv64 one with the
store form
narrowed to sw, the only width this port has. clone.s carries no
end-of-stack
marker and needs none.
Verified by assembling both files with and without the generator using
-march=rv32i -mabi=ilp32 in the riscv64 Alpine container and reading the FDE
coverage back out; Alpine has no riscv32 image, so musl was not built as a
whole here.
---
--- /dev/null
+++ b/tools/add-cfi.riscv32.awk
@@ -0,0 +1,125 @@
+# Insert GAS CFI directives ("control frame information") into riscv32
asm input
+#
+# No riscv32 asm moves the stack pointer today - syscall_cp.s and clone.s
+# among them - so the default rules already describe those frames and the
+# generator only has to open and close them. The two rules at the
bottom are
+# for the files that do; the riscv64 tree has one, ldso/riscv64/tlsdesc.s.
+
+BEGIN {
+ # don't put CFI data in the .eh_frame ELF section (which we don't keep)
+ print ".cfi_sections .debug_frame"
+
+ # only emit CFI directives inside a function
+ in_function = 0
+
+ # emit .loc directives with line numbers from original source
+ printf ".file 1 \"%s\"\n", ARGV[1]
+ line_number = 0
+}
+
+function adjust_sp_offset(delta) {
+ if (in_function)
+ printf ".cfi_adjust_cfa_offset %d\n", delta
+}
+
+{
+ line_number = line_number + 1
+
+
+ # clean the input up before doing anything else
+ # delete comments
+ gsub(/(#).*/, "")
+
+ # canonicalize whitespace
+ gsub(/[ \t]+/, " ") # mawk doesn't understand \s
+ gsub(/ *, */, ",")
+ gsub(/ *: */, ": ")
+ gsub(/ $/, "")
+ gsub(/^ /, "")
+
+ # A label may share the line with the instruction it marks. The rules
below
+ # match on this, so they do not have to care either way, while the line
+ # itself is still printed with its label intact.
+ insn = $0
+ sub(/^[a-zA-Z0-9_]+: /, "", insn)
+}
+
+# check for assembler directives which we care about
+/^\.(section|data|text)/ {
+ # a .cfi_startproc/.cfi_endproc pair should be within the same section
+ # otherwise, clang will choke when generating ELF output
+ if (in_function) {
+ print ".cfi_endproc"
+ in_function = 0
+ }
+}
+/^\.type [a-zA-Z0-9_]+,%function/ {
+ functions[substr($2, 1, length($2)-10)] = 1
+}
+# not interested in assembler directives beyond this, just pass them
through
+/^\./ {
+ print
+ next
+}
+
+/^[a-zA-Z0-9_]+:/ {
+ label = substr($1, 1, length($1)-1) # drop trailing :
+
+ if (functions[label]) {
+ if (in_function)
+ print ".cfi_endproc"
+
+ in_function = 1
+ print ".cfi_startproc"
+
+ for (register in saved)
+ delete saved[register]
+ }
+
+ # an instruction may follow on the same line, so continue processing
+}
+
+
+/^$/ { next }
+
+{
+ printf ".loc 1 %d\n", line_number
+ print
+}
+
+# KEEPING UP WITH THE STACK POINTER
+# "add sp,sp,-n" opens a frame and "add sp,sp,n" closes it again - the
operand
+# carries the sign, and the CFA offset moves the other way. Nothing else in
+# this source tree writes sp.
+#
+insn ~ /^addi? sp,sp,-?[0-9]+$/ {
+ if (in_function) {
+ n = insn
+ sub(/^addi? sp,sp,/, "", n)
+ adjust_sp_offset(-(n + 0))
+ }
+}
+
+# TRACKING REGISTER VALUES FROM THE PREVIOUS STACK FRAME
+# "sw ra,n(sp)" and friends put a register from the caller on the stack. A
+# store written without an offset - "sw t1,(sp)" - is passed over
rather than
+# read as 0: leaving a register untracked costs a value in the
debugger, while
+# guessing at the address would print the wrong one.
+#
+insn ~ /^sw [a-z0-9]+,[0-9]+\(sp\)$/ {
+ if (in_function) {
+ register = substr(insn, index(insn, " ")+1, index(insn,
",")-index(insn, " ")-1)
+ offset = substr(insn, index(insn, ",")+1)
+ sub(/\(sp\)$/, "", offset)
+ if (!saved[register]) {
+ printf ".cfi_rel_offset %s,%d\n", register, offset+0
+ saved[register] = 1
+ }
+ }
+}
+
+
+END {
+ if (in_function)
+ print ".cfi_endproc"
+}
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.