As other comments have noted, the asm statement needs to have its input/output registers specified to ensure the compiler doesn't erase the "unused" values.
> This C program doesn’t use any C standard library functions.
This is only half true. While the code doesn't call any stdlib functions, it still relies on the the c stdlib and runtime in order to get called and properly exit.
I'm somewhat perplexed why the author did do it with the runtime, given that he doesn't really depend on features of it (except maybe the automatic exit code handling) instead of building with -ffreestanding.
Thanks for making me extremely sentimental for the hundreds of Turbo Pascal projects I did back in the day - this particular example highlights the elegance and clarity of the language, which we still seem to resist in our modern tooling.
The code biffs rax when it loads the string address, so the system call number is lost, and the code ends up not printing anything. Moving the string assignment to be the very first line in main fixes it.
BTW, Clang 14 with no optimization accepts the code without issue but compiles it without using any of the registers; it just stores the values to memory locations and runs the syscall opcode. With O1 optimization or higher, it optimizes away everything except the syscall opcode.
No idea why a newer version produces worse code in this case (though of course, this way of doing inline assembly isn't "correct" anyway, so nasal demons may result)
Never seen inline assembly written quite like that, is this actually correct code? I'm concerned that normally register annotation is just a hint, and that the assembly blocks are not marked volatile - and that the compiler may therefore be free to rewrite this code in many breaking ways.
Edit: Ah a basic asm blocks is implicitly volatile. I'm still a little concerned the compiler could get clever and decide the register variables are unused and optimize them out.
I think that named register variables (a GCC extension) are meant to be live in asm block by design, so they shouldn't be optimized away.
Still I would use extended asm.
edit: from the docs: "The only supported use for [Specifying Registers for Local Variables] is to specify registers for input and output operands when calling Extended asm".
It's not UB, it's documented behaviour of a vendor extension.
It's not UB because it's defined as outside the scope of the language standard. The vendor (in this case, GCC) does document how to use its inline assembly extension in quite a lot of detail, including how to use clobber lists to prevent exactly the kind of thing these failures demonstrate.
As other comments have noted, the asm statement needs to have its input/output registers specified to ensure the compiler doesn't erase the "unused" values.
Working example: https://john-millikin.com/unix-syscalls#linux-x86-64-gnu-c
Adapted to use main():
Test with:
Or just
Though the clobber list is weak spot, I don't know exactly what it should have in this case.
You want:
You can also say:
https://justine.lol/dox/rmsiface.txt
3 replies →
> This C program doesn’t use any C standard library functions.
This is only half true. While the code doesn't call any stdlib functions, it still relies on the the c stdlib and runtime in order to get called and properly exit.
I'm somewhat perplexed why the author did do it with the runtime, given that he doesn't really depend on features of it (except maybe the automatic exit code handling) instead of building with -ffreestanding.
You have to add some extra assembly before main if you don't use the C runtime. You have to write _start, the actual entry point that CRT usually takes. https://github.com/fsmv/dfre/blob/master/code/linux32_start....
This is for -nostdlib not -ffreestanding
You can usually with not having the initial part. As long as you do call the exit syscall, it should work.
"This C program doesn’t explicitly use any C standard library functions." doesn't sound as cool, though.
If you ever feel the need to do this in production, use linux_syscall_support.h (LSS) https://chromium.googlesource.com/linux-syscall-support
No need to remember syscall numbers or calling conventions, or the correct way to annotate your __asm__ directives, and it's even cross-architecture.
Actually more readable than the AT&T syntax :)
But does this work on both GCC and Clang, and is safe from being optimized away? edit: the answer is no
Turbo Pascal had an integrated assembler that could use symbols (and even complex types) defined anywhere in the program, like this:
Not only Turbo Pascal, this more sane approach to inline Assembly was quite common in the PC world compilers, regardless of the programming language.
Inline assembly also has support for symbol names, although the native symbols could not be accessed directly but in a bit awkward way.
https://stackoverflow.com/questions/32131950/assembler-templ...
Thanks for making me extremely sentimental for the hundreds of Turbo Pascal projects I did back in the day - this particular example highlights the elegance and clarity of the language, which we still seem to resist in our modern tooling.
I don't really see what's "elegant" about the code, could you elaborate? (This isn't a jab at GP. I'm just curious about what I'm not seeing.)
4 replies →
When I compile it with GCC 12, this machine code results:
Can you spot the error?
. . . . . .
The code biffs rax when it loads the string address, so the system call number is lost, and the code ends up not printing anything. Moving the string assignment to be the very first line in main fixes it.
BTW, Clang 14 with no optimization accepts the code without issue but compiles it without using any of the registers; it just stores the values to memory locations and runs the syscall opcode. With O1 optimization or higher, it optimizes away everything except the syscall opcode.
The exact same thing happens with GCC 12 with 32-bit MIPS.
With an older version, it works (as long as there is no optimization at least, with -O2 all the register init code disappears):
$ gcc -v
... gcc version 10.2.1 20210110 (Debian 10.2.1-6)
No idea why a newer version produces worse code in this case (though of course, this way of doing inline assembly isn't "correct" anyway, so nasal demons may result)
Never seen inline assembly written quite like that, is this actually correct code? I'm concerned that normally register annotation is just a hint, and that the assembly blocks are not marked volatile - and that the compiler may therefore be free to rewrite this code in many breaking ways.
Edit: Ah a basic asm blocks is implicitly volatile. I'm still a little concerned the compiler could get clever and decide the register variables are unused and optimize them out.
Tried it with GCC, and without any optimization it does print the message. With "-O2" however, we get this:
Everything except the syscall instruction has been optimized away!
Now that's incredibly cursed. Could do basically anything and swallows the error too!
I think that named register variables (a GCC extension) are meant to be live in asm block by design, so they shouldn't be optimized away.
Still I would use extended asm.
edit: from the docs: "The only supported use for [Specifying Registers for Local Variables] is to specify registers for input and output operands when calling Extended asm".
So the example is UB.
It's not UB, it's documented behaviour of a vendor extension.
It's not UB because it's defined as outside the scope of the language standard. The vendor (in this case, GCC) does document how to use its inline assembly extension in quite a lot of detail, including how to use clobber lists to prevent exactly the kind of thing these failures demonstrate.
5 replies →
The `return 0;` is optional for main() in C, so the function body could be made to consist solely of inline assembly.
Is anyone aware of a similar example, for ARM assembly on macOS?
How does one compile this?
EDIT: my bad, my source had a typo - it's as easy as you'd think:
thanks!
Not inline, but this was linked in a comment on HN a few days ago
https://github.com/below/HelloSilicon
Try this with visual studio and x64. Microsoft!!