1

I've built a short program written on C and inline assembly on my linux x86_64. It is supposed to write a string to stdout. I found it in an article on the internet:

int write_call( int fd, const char * str, int len ){
    long __res;
    __asm__ volatile ( "int $0x80":
        "=a" (__res):"0"(__NR_write),"b"((long)(fd)),"c"((long)(str)),"d"((long)(len)) );
    return (int) __res;
}

void do_write( void ){
    char * str = "Paragon output string.\n";
    int len = strlen( str ), n;
    printf( "string for write length = %d\n", len );
    n = write_call( 1, str, len );
    printf( "write return : %d\n", n );
}

int main( int argc, char * argv[] ){
    do_write();
    return EXIT_SUCCESS;
}

But as I run it, it works incorrectly, making output

"write return : -14"

If I build and run it on 32-bit linux it does what is expected.

After some research I fount out that instruction "int $0x80" is a x86 instruction and truncates arguments in registers if called on x86_64.

But I couldn't find a proper substitution of "int $0x80" for x86_64 architecture. I have zero experience in assembly.

What should I put instead of "int $0x80" to receive expected result?

kopalvich
  • 434
  • 5
  • 14
  • 2
    Use the `write()` function found in glibc. If you want to know how it works, disassemble the `write()` function in glibc. – Ben Voigt Apr 11 '14 at 18:53

1 Answers1

5

For amd64, you need to use "syscall" - and use different registers - instead of "int 0x80":

http://cs.lmu.edu/~ray/notes/linuxsyscalls/

http://blog.rchapman.org/post/36801038863/linux-system-call-table-for-x86-64

http://crypto.stanford.edu/~blynn/rop/

Here's a good example:

How to invoke a system call via sysenter in inline assembly (x86/amd64 linux)?

#include <unistd.h>

int main(void)
{
    const char hello[] = "Hello World!\n";
    const size_t hello_size = sizeof(hello);
    ssize_t ret;
    asm volatile
    (
        "movl $1, %%eax\n\t"
        "movl $1, %%edi\n\t"
        "movq %1, %%rsi\n\t"
        "movl %2, %%edx\n\t"
        "syscall"
        : "=a"(ret)
        : "g"(hello), "g"(hello_size)
        : "%rdi", "%rsi", "%rdx", "%rcx", "%r11"
    );
    return 0;
Community
  • 1
  • 1
FoggyDay
  • 11,962
  • 4
  • 34
  • 48
  • thanks, but is `__NR_write` macro not needed anymore? – kopalvich Apr 11 '14 at 19:01
  • __NR_write is still there, in [unistd_64.h](http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/arch/x86/include/asm/unistd_64.h#L17). It simply corresponds to "1", loaded into eax before invoking syscall. – FoggyDay Apr 11 '14 at 19:18
  • 1
    Note that while this assembler code does work, it is not well written. A better version is available in the [link](http://stackoverflow.com/a/9508738/2189500) FoggyDay mentions. – David Wohlferd Jan 13 '17 at 01:27
  • 1
    @kopalvich: the real problem with your original was that the `__NR_write` and so on macros *have different numbers* for `syscall` vs. the 32-bit `int 0x80` ABI. [Using `int 0x80` in 64-bit code is supported but *not* recommended because of various limitations](https://stackoverflow.com/questions/46087730/what-happens-if-you-use-the-32-bit-int-0x80-linux-abi-in-64-bit-code). – Peter Cordes Mar 14 '18 at 12:14