Home > Blockchain >  gnu GAS use stdio.h in asm and compile 32 bit on 64 bit host
gnu GAS use stdio.h in asm and compile 32 bit on 64 bit host

Time:09-17

I want to compile the following asm.S file

#include <stdio.h>
.global _start
.text
message_text:
  call program
str .string "hello world"
  .equ len, (. - str)

_start:
  jmp message_text
program:
  call puts@PLT

I have tried gcc -m32 asm.S as well as gcc -m32 asm.S -fPIC and a litany of other things but I keep running into errors such as /usr/include/stdio.h:847: Error: no such instruction: extern void ... what am I doing wrong?

I get that the preprocessor is just inserting the header into the asm file and so the assembler is getting erroring out b/c C function prototypes are not assembly instructions, however I have been able to use #include <asm/unistd.h> and #include <syscall.h> with success. So why won't stdio.h work?

As an aside, I tried commenting out the header and just keeping the line call puts@PLT and was able to compile via

as --32 -o output.o asm.S
ld -m elf_i386 -o output output.o -lc

However now when I try ./output I get no such file or dirctory: ./output despite output showing up in ls and if I run file output I get output: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /usr/bin/libc.so.1, not stripped

If I instead do

as --32 -o output.o asm.S
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o output -lc output.o

I can run the resulting binary. But again, this is if I remove #include <stdio.h>.

CodePudding user response:

stdio.h is a C header with C syntax, not just C preprocessor stuff. In asm you don't need C prototypes or variable declarations; in GAS any undeclared symbol is assumed to be extern so you can just call puts@plt without any other directives. (Unlike NASM where you would need extern puts, which is different syntax from C so the C header wouldn't help anyway.)

A few C headers like <asm/unistd.h> contain only #if and #define preprocessor directives, and can be included in .S files as well. Most headers can't, although the Linux kernel's asm/*.h typically can.

Standard headers could have been written to use #ifndef __ASSEMBLY__ around C specific parts, which would have been useful for headers like <fcntl.h> which have constants like O_RWDR and O_TRUNC as well as C prototypes, but they weren't.

The stdio API predates the C preprocessor, which is why it unfortunately uses strings like fopen(name, "r ") instead of ORing constants. So this isn't necessary or useful on stdio.h.

See also:


BTW, your jmp / call trick to push a string address looks like shellcode. But you can't call puts@plt in shellcode because the relative offset from a stack buffer to that PLT entry will be randomized.

Or perhaps it's just a hacky way to make 32-bit PIC code, in which case it's compact-ish but inefficient: defeats return-address prediction. (Mismatched call/ret). Look at how gcc or clang -O2 -m32 -fPIC compiles C code, getting an address for the GOT into a register with call; note that call next_instruction (relative offset of 0) is special cased to not count for return-address prediction on most CPUs. See Reading program counter directly

This is clang -O2 -m32 -fPIE output from the Godbolt compiler explorer with necessary directives kept, unnecessary ones manually removed. (Godbolt's directives filter will normally filter everything, assuming that you know which ones are needed.)

This is the standard way to get a static address into a register in position-independent 32-bit code. (Nevermind that it's a main so it returns instead of calling exit. Or _exit or a raw system call, but don't do that in programs that use stdio functions; that wouldn't flush stdout if there was buffered output left, e.g. because of writing to a pipe.)

        .text
        .globl  main                            # -- Begin function main
main:
        pushl              
  • Related