Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[llvm][ARM] Unable to use .cantunwind (and similar directives) in inline assembly #115891

Open
alexrp opened this issue Nov 12, 2024 · 7 comments

Comments

@alexrp
Copy link
Member

alexrp commented Nov 12, 2024

void _start(void)
{
    asm volatile (".cantunwind");
}
arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc (Ubuntu 13.2.0-4ubuntu3) 13.2.0arm-linux-gnueabihf-gcc test.c -nostdlib -fasynchronous-unwind-tablesclang --version
clang version 19.1.2 (git@github.com:llvm/llvm-project.git d5498c39fe6a17e271ad5a02917103445eb89373)clang --target=arm-linux-gnueabihf test.c -nostdlib -fasynchronous-unwind-tables
test.c:3:19: error: .fnstart must precede .cantunwind directive
    3 |     asm volatile (".cantunwind");
      |                   ^
<inline asm>:1:2: note: instantiated into assembly here
    1 |         .cantunwind
      |         ^
1 error generated.

I would guess that .fnstart/.fnend is lacking similar handling to .cfi_startproc/.cfi_endproc in the integrated assembler?

@llvmbot
Copy link
Member

llvmbot commented Nov 12, 2024

@llvm/issue-subscribers-backend-arm

Author: Alex Rønne Petersen (alexrp)

```c void _start(void) { asm volatile (".cantunwind"); } ```
arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc (Ubuntu 13.2.0-4ubuntu3) 13.2.0arm-linux-gnueabihf-gcc test.c -nostdlib -fasynchronous-unwind-tablesclang --version
clang version 19.1.2 (git@<!-- -->github.com:llvm/llvm-project.git d5498c39fe6a17e271ad5a02917103445eb89373)clang --target=arm-linux-gnueabihf test.c -nostdlib -fasynchronous-unwind-tables
test.c:3:19: error: .fnstart must precede .cantunwind directive
    3 |     asm volatile (".cantunwind");
      |                   ^
&lt;inline asm&gt;:1:2: note: instantiated into assembly here
    1 |         .cantunwind
      |         ^
1 error generated.

I would guess that .fnstart/.fnend is lacking similar handling to .cfi_startproc/.cfi_endproc in the integrated assembler?

alexrp added a commit to alexrp/zig that referenced this issue Nov 20, 2024
The function is not marked .cantunwind for Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Nov 20, 2024
Whatever was in the frame pointer register prior to clone() will no longer be
valid in the child process, so zero it to protect FP-based unwinders. Similarly,
mark the link register as undefined to protect DWARF-based unwinders.

This is only zeroing the frame pointer(s) on Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Nov 20, 2024
The function is not marked .cantunwind for Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Nov 20, 2024
Whatever was in the frame pointer register prior to clone() will no longer be
valid in the child process, so zero it to protect FP-based unwinders. Similarly,
mark the link register as undefined to protect DWARF-based unwinders.

This is only zeroing the frame pointer(s) on Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Nov 20, 2024
The function is not marked .cantunwind for Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Nov 20, 2024
Whatever was in the frame pointer register prior to clone() will no longer be
valid in the child process, so zero it to protect FP-based unwinders. Similarly,
mark the link register as undefined to protect DWARF-based unwinders.

This is only zeroing the frame pointer(s) on Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Nov 20, 2024
The function is not marked .cantunwind for Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Nov 20, 2024
Whatever was in the frame pointer register prior to clone() will no longer be
valid in the child process, so zero it to protect FP-based unwinders. Similarly,
mark the link register as undefined to protect DWARF-based unwinders.

This is only zeroing the frame pointer(s) on Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Dec 6, 2024
The function is not marked .cantunwind for Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Dec 6, 2024
Whatever was in the frame pointer register prior to clone() will no longer be
valid in the child process, so zero it to protect FP-based unwinders. Similarly,
mark the link register as undefined to protect DWARF-based unwinders.

This is only zeroing the frame pointer(s) on Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Dec 12, 2024
The function is not marked .cantunwind for Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
alexrp added a commit to alexrp/zig that referenced this issue Dec 12, 2024
Whatever was in the frame pointer register prior to clone() will no longer be
valid in the child process, so zero it to protect FP-based unwinders. Similarly,
mark the link register as undefined to protect DWARF-based unwinders.

This is only zeroing the frame pointer(s) on Arm/Thumb because of an LLVM
assembler bug: llvm/llvm-project#115891
@efriedma-quic
Copy link
Collaborator

If you define a complete function inside an asm block, .cantunwind should work. If you're writing code within a function, that's more restricted: inline asm blocks are parsed separately. It isn't just blindly interpolated into the function, so you can't modify a function like this.

__attribute__((nouwtable)) should do what you want.

@alexrp
Copy link
Member Author

alexrp commented Jan 22, 2025

Just to clarify: I'm using C in the example, but I actually ran into the issue in Zig.

If you define a complete function inside an asm block, .cantunwind should work. If you're writing code within a function, that's more restricted: inline asm blocks are parsed separately. It isn't just blindly interpolated into the function, so you can't modify a function like this.

I don't quite understand why this would have to be the case for .cantunwind when e.g. .cfi_undefined and other similar directives work fine in inline assembly?

__attribute__((nouwtable)) should do what you want.

(We don't have an __attribute__((nouwtable)) equivalent in Zig.)

@efriedma-quic
Copy link
Collaborator

.cfi_undefined outside a function is silently discarded instead of emitting an error. Maybe we could do the same for .cantunwind, I guess, but it seems sort of weird.

We don't make promises about the exact structure of emitted assembly outside the asm block, though, so I wouldn't encourage using exception-handling directives like that in any case.

@alexrp
Copy link
Member Author

alexrp commented Jan 22, 2025

.cfi_undefined outside a function is silently discarded instead of emitting an error.

That doesn't seem right. This:

int main(void)
{
    asm volatile (".cfi_undefined %r13");
    asm volatile (".cfi_undefined %r14");
    asm volatile (".cfi_undefined %r15");
}

compiled with clang -fexceptions gets me:

00000070 00000020 00000074 FDE cie=00000000 pc=00001130...00001138
  Format:       DWARF32
  DW_CFA_advance_loc: 1 to 0x1131
  DW_CFA_def_cfa_offset: +16
  DW_CFA_offset: RBP -16
  DW_CFA_advance_loc: 3 to 0x1134
  DW_CFA_def_cfa_register: RBP
  DW_CFA_undefined: R13
  DW_CFA_undefined: R14
  DW_CFA_undefined: R15
  DW_CFA_advance_loc: 3 to 0x1137
  DW_CFA_def_cfa: RSP +8
  DW_CFA_nop:

  0x1130: CFA=RSP+8: RIP=[CFA-8]
  0x1131: CFA=RSP+16: RBP=[CFA-16], RIP=[CFA-8]
  0x1134: CFA=RBP+16: RBP=[CFA-16], R13=undefined, R14=undefined, R15=undefined, RIP=[CFA-8]
  0x1137: CFA=RSP+8: RBP=[CFA-16], R13=undefined, R14=undefined, R15=undefined, RIP=[CFA-8]

@efriedma-quic
Copy link
Collaborator

To be more precise:

There are two layers involved here: the "parser", and the "streamer". The parser converts text into an internal representation, and the streamer processes it. For stuff that isn't inline asm, the compiler normally just pokes the streamer directly.

The error checking here is part of the "parser". The Arm asm parser does its own tracking for whether there's a .fnstart. The compiler's .fnstart doesn't use the asm parser to emit .fnstart (it skips straight to the streamer), so the asm parser thinks there's a missing .fnstart, so it emits an error.

The generic CFI parsing doesn't track anything; it just throws it all at the asm streamer. And the streamer will silently discard directives. But it will also allow mixing up directives with the ones the compiler emits internally.

Maybe there's some solution where we can make the parser cooperate with the streamer to emit error messages.

@alexrp
Copy link
Member Author

alexrp commented Jan 24, 2025

Ah, I see what you mean now. That makes sense.

If my understanding of the parser/printer/streamer abstractions is correct (it very well might not be!), then it should be sufficient to add state tracking for emitFnStart()/emitFnEnd() calls to ARMTargetStreamer such that ARMAsmParser can query it. It might even make sense for most of the state tracking related to EH directives in ARMAsmParser to be moved there. Does this sound right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants