2

溢出,

如何仅使用内联汇编来实现 putchar(char) 过程?我想在 x86-64 程序集中执行此操作。我这样做的原因是实现我自己的标准库(或至少部分)。这是我到目前为止所拥有的:

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

谢谢你帮助我!

4

3 回答 3

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"
                );
}
于 2013-03-24T11:18:27.780 回答
2

请注意,getchar(3)/是宏(用于性能),用于处理/结构putchar(3)中的复杂数据,特别是处理缓冲和其他。nrz 的答案只是对文件描述符 1 做了一个 1 字符,这是非常不同的。FILEstdinstdoutwrite(3)

于 2013-03-25T01:56:31.230 回答
2

当使用 GNU C 内联 asm 时,使用约束来告诉编译器你想要的东西,而不是使用 asm 模板中的指令“手动”执行它。

对于writecharand readchar,我们只需要 a"syscall"作为模板,根据x86-64 Linux 系统调用约定(char非常接近匹配 System V ABI 的函数调用约定)。 i386 和 x86-64 上的 UNIX 和 Linux 系统调用的调用约定是什么write(2)

这也可以轻松避免破坏编译器可能保留值的红色区域(低于 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必须检查字符以查看它们是否与格式字符串匹配,如果不匹配则将它们设为“未读”。不过,输出缓冲是可选的;你可以我认为完全实现 stdio 与创建私有缓冲区和write()它的函数,或直接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;
}

这在 Godbolt 编译器资源管理器上使用 gcc -O3非常有效地编译。

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有效地内联它,利用了大多数寄存器未修改的事实,因此它可以只设置一次。syscall

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

readchar(),以同样的方式完成:

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
}

调用者可以通过检查来检查错误c < 0

于 2018-06-05T02:35:19.363 回答