2

I am trying to write a function, which prints string to stdout without importing <cstdio> or <iostream>.

For this I am trying to pass 2 parameters (const char* and const unsigned) to the asm(...) section in c++ code. And calling write syscall.

This works fine:

void writeInAsm(const char* str, const unsigned len) {
    register const char* arg3 asm("rsi") = str; 
    register const unsigned arg4 asm("rdx") = len;
    asm(
        "mov rax, 1 ;" // write syscall
        "mov rdi, 1 ;" // file descriptor 1 - stdout
        "syscall ;"
    );
}

Is it possible to do this without those first two lines in which I assign parameters to registers?

Next lines don't work:

mov rsi, str; 
// error: relocation R_X86_64_32S against undefined symbol `str' can not be used when making a PIE object; recompile with -fPIC
// compiled with -fPIC - still got this error
mov rsi, [str]; 
// error: relocation R_X86_64_32S against undefined symbol `str' can not be used when making a PIE object; recompile with -fPIC
// compiled with -fPIC - still got this error

mov rsi, dword ptr str; 
// incorrect register `rsi' used with `l' suffix
mov rsi, dword ptr [str]; 
// incorrect register `rsi' used with `l' suffix

I am compiling with g++ -masm=intel. I am on x86_64 Intel® Core™ i7-7700HQ CPU @ 2.80GHz × 8, Ubuntu 19.04 5.0.0-36-generic kernel (if it matters).

$ g++ --version 
g++ (Ubuntu 8.3.0-6ubuntu1) 8.3.0

Edit: According to Compiler Explorer, the next can be used:

void writeInAsm(const char* str, const unsigned len) {
    asm(
        "mov rax, 1 ;"
        "mov rdi, 1 ;"
        "mov rsi, QWORD PTR [rbp-8] ;"
        "mov edx, DWORD PTR [rbp-12] ;"
        "syscall ;"
    );
}

But is it always rbp register and how will it change with larger number of parameters?

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
a_girl
  • 959
  • 7
  • 22
  • I suggest you read the wiki part of the tag x86. There is a bunch of informations there. – AProgrammer Nov 17 '19 at 14:59
  • Using `rbp` register is actually passing parameters by stack. But where the parameters are located depends on your compiler. – KagurazakaKotori Nov 17 '19 at 16:32
  • I doubt `register` keyword has the intended effect. AFAIR, it was removed years ago. – Tanveer Badar Nov 17 '19 at 16:54
  • 1
    First if you want to access memory like data at pointers on the stack etc do not use basic inline assembly. Use GCC's [extended inline assembly](https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html) and pass the parameters via constraints. Also be aware that RCX and R11 are clobbered by the syscall instruction and a return value / error is returned by Linux in RAX. – Michael Petch Nov 17 '19 at 17:15
  • And of course with the Linux System V 64-bit ABI he first 6 integer class parameters are in registers rather than the stack (unlike the Linux System V 32-bit ABI). The first integer class parameters are passed via RDI, RSI, RDX, RCX, R8, and R9. – Michael Petch Nov 17 '19 at 17:56
  • 1
    An example on godbolt that would work: https://godbolt.org/z/6_Qgse . There are two versions of the function that do same thing but one avoids using a memory clobber – Michael Petch Nov 17 '19 at 18:03
  • 2
    @TanveerBadar: GNU C register-asm local variables aren't *officially* supported anymore for anything except forcing an `"r"` constraint to pick a specific register, but the internal implementation does still happen do what the OP wants in this case. Of course the whole thing is totally broken from stepping on the compiler's registers and making assumptions about the stack frame, but the `register ... asm("rsi")` part is legal and happens to actually work. https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html – Peter Cordes Nov 17 '19 at 18:43
  • Working x86-64 answer on [How to invoke a system call via sysenter in inline assembly?](//stackoverflow.com/q/9506353) – Peter Cordes Nov 17 '19 at 19:58

0 Answers0