GCC:putchar(char)在内联汇编中

时间:2013-03-24 08:59:02

标签: linux gcc assembly x86-64 inline-assembly

溢出,

如何仅使用内联汇编实现putchar(char)过程?我想在x86-64汇编中这样做。我这样做的原因是实现我自己的标准-lib(或至少部分)。以下是我到目前为止的情况:

void putchar(char c)
{   
    /* your code here: print character c on stdout */
    asm(...);
}   

void _start()
{   
    /* exit system call */
    asm("mov $1,%rax;"
        "xor %rbx,%rbx;"
        "int  $0x80"
    );  
}

我正在编译:

gcc -nostdlib -o putchar putchar.c

感谢您的帮助!

3 个答案:

答案 0 :(得分:3)

以下是GCC x86-64内联汇编中的示例my_putchar(在Intel语法中,转换为AT& T应该是微不足道的。)

编译:

gcc -ggdb -masm=intel -o gcc_asm_putchar gcc_asm_putchar.c
被破坏的寄存器中缺少

编辑: rdi。固定的。

以下是代码:

int main(void)
{
    char my_char;

    for (my_char = 'a'; my_char <= 'z'; my_char++)
            my_putchar(my_char);

    my_char = '\n';
    my_putchar(my_char);
    return 0;
}

void my_putchar(char my_char)
{
    int dword_char;
    dword_char = (int)my_char;
    asm volatile(
                    ".intel_syntax noprefix;"
                    "mov r10,rsp;"   // save rsp.
                    "sub rsp,8;"     // space for buffer, align by 8.
                    "mov [rsp],al;"  // store the character into buffer.
                    "mov edi,1;"     // STDOUT.
                    "mov rsi,rsp;"   // pointer to buffer.
                    "mov edx,1;"     // string length in bytes.
                    "mov eax,1;"     // WRITE.
                    "syscall;"       // clobbers rcx & r11.
                    "mov rsp,r10;"   // restore rsp.
                    ".att_syntax prefix;"
                    /* outputs */
                    :
                    /* inputs: eax */
                    : "a"(dword_char)
                    /* clobbered regs */
                    : "rcx", "rdx", "rsi", "rdi", "r10", "r11"
                );
}

答案 1 :(得分:2)

请注意,getchar(3) / putchar(3)是宏(用于提高性能),它会混淆FILE / stdin的{​​{1}}结构中的复杂数据处理缓冲和其他。 nrz的答案只是对文件描述符1进行了1个字符stdout,其中非常不同。

答案 2 :(得分:2)

当使用GNU C inline asm时,使用约束来告诉编译器你想要的东西,而不是这样做&#34;手动&#34;在asm模板中有说明。

对于writecharreadchar,我们只需要"syscall"作为模板,并在约束中设置寄存器中的所有输入(以及指向char在内存中为the write(2) system call),根据x86-64 Linux系统调用约定(它与System V ABI的函数调用约定非常匹配)。 What are the calling conventions for UNIX & Linux system calls on i386 and x86-64

这也可以很容易地避免破坏红区(RSP下面128个字节),编译器可能会保留值。你不能从内联asm中删除它(所以push / pop不可用,除非你先sub rsp, 128:请参阅Using base pointer register in C++ inline asm以及有关GNU的许多有用链接C inline asm),并且没有办法告诉编译器你破坏它。您可以使用-mno-redzone进行构建,但在这种情况下,输入/输出操作数要好得多。

我对调用这些putchargetchar犹豫不决。如果您正在实现自己尚不支持缓冲的stdio,但是某些函数需要输入缓冲才能正确实现,您可以这样做。例如,scanf必须检查字符以查看它们是否与格式字符串匹配,并保留它们&#34;未读&#34;如果他们不这样做。但输出缓冲是可选的; 可以我认为完全使用创建私有缓冲区和write()它的函数实现stdio,或者直接write()它们的输入指针。

writechar()

int writechar(char my_char)
{
    int retval;  // sys_write uses ssize_t, but we only pass len=1
                 // so the return value is either 1 on success or  -1..-4095 for error
                 // and thus fits in int

    asm volatile("syscall  #dummy arg picked %[dummy]\n"
                    : "=a" (retval)  /* output in EAX */
                    /* inputs: ssize_t read(int fd, const void *buf, size_t count); */
                    : "D"(1),         // RDI = fd=stdout
                      "S"(&my_char),  // RSI = buf
                      "d"(1)          // RDX = length
                      , [dummy]"m" (my_char) // dummy memory input, otherwise compiler doesn't store the arg
                    /* clobbered regs */
                    : "rcx", "r11"  // clobbered by syscall
                );
    // It doesn't matter what addressing mode "m"(my_char) picks,
    // as long as it refers to the same memory as &my_char so the compiler actually does a store

    return retval;
}

这非常有效地编译with gcc -O3, on the Godbolt compiler explorer

writechar:
    movb    %dil, -4(%rsp)        # store my_char into the red-zone
    movl    $1, %edi
    leaq    -4(%rsp), %rsi
    movl    %edi, %edx            # optimize because fd = len
    syscall               # dummy arg picked -4(%rsp)

    ret

@ nrz的测试main比那个答案中的不安全(红区破坏)版本更有效地内联它 ,利用{{1}的事实大多数寄存器都是未经修改的,所以它只需设置一次。

syscall

readchar(),完成相同的方式:

main:
    movl    $97, %r8d            # my_char = 'a'
    leaq    -1(%rsp), %rsi       # rsi = &my_char
    movl    $1, %edx             # len
.L6:                           # do {
    movb    %r8b, -1(%rsp)       # store the char into the buffer
    movl    %edx, %edi           # silly compiler doesn't hoist this out of the loop
    syscall  #dummy arg picked -1(%rsp)

    addl    $1, %r8d
    cmpb    $123, %r8b
    jne     .L6                # } while(++my_char < 'z'+1)

    movb    $10, -1(%rsp)
    syscall  #dummy arg picked -1(%rsp)

    xorl    %eax, %eax         # return 0
    ret

来电者可以通过查看int readchar(void) { int retval; unsigned char my_char; asm volatile("syscall #dummy arg picked %[dummy]\n" /* outputs */ : "=a" (retval) ,[dummy]"=m" (my_char) // tell the compiler the asm dereferences &my_char /* inputs: ssize_t read(int fd, void *buf, size_t count); */ : "D"(0), // RDI = fd=stdin "S" (&my_char), // RDI = buf "d"(1) // RDX = length : "rcx", "r11" // clobbered by syscall ); if (retval < 0) // -1 .. -4095 are -errno values return retval; return my_char; // else a 0..255 char / byte } 来查看错误。