从汇编中使用getchar的gets()函数

时间:2018-09-02 20:25:03

标签: c assembly dos x86-16

我在为一个班级做的C代码上执行gets()函数时遇到一些问题。所以我已经有了一个getchar()函数,但是在汇编时,我正在使用extern从C调用它,问题是,在我运行代码的那一刻,我输入了一个字符串,但它没有显示完整的字符串而不是一些字符。

这是我有atm的代码: C代码:

extern char getchar(void);
extern void putchar(char data);
void gets(char *str);
void puts(char *str);
void new_line();

char string[20];

int main(){
    while(1){
        gets(string);
        new_line();
        puts(string);
    }
    return 0;
}

void new_line(){
    putchar(0xD);
    putchar(0xA);
}
void gets(char *str){
    unsigned char i = 0;
    while((*str = getchar()) != 0xD){
        str[i] = getchar();
        i++;
    }
}

void puts(char *str){
    while(*str){
        putchar(*str++);
    }
}

和我的ASM代码,以防万一:

.MODEL tiny

.CODE
    public _putchar
    public _getchar

    _putchar    PROC
                push bp
                mov bp, sp
                mov dl, [bp + 4]
                mov ah, 2
                int 21h
                pop bp
                ret
    _putchar    ENDP

    _getchar    PROC
                push bp
                mov bp, sp
                mov ah, 1
                int 21h
                mov [bp + 4], al
                pop bp
                ret
    _getchar    ENDP
END

我正在使用MTTTY和我们的老师提供的8086解释器在Arduino Mega上运行代码。

有什么办法可以使用gets()函数解决此问题,以便我可以正确显示输入字符串?

例如,如果我输入“ hello world”,它只会打印“ l ol”

1 个答案:

答案 0 :(得分:6)

您的C gets实现被破坏,而与asm getchar实现无关。您可以使用普通的调试器在台式机上的普通C实现中对其进行调试。

您两次致电getchar(),并且仅保存每第二个结果。

第一个结果分配给str[0]并检查'\r'

// your version with comments
void gets_original_buggy (char *str){
    unsigned char i = 0;   // this is an index; it should be an `int` or `size_t`

    while((*str = getchar()) != 0xD){  // overwrite the first byte of the string with an input
        str[i] = getchar();    // get ANOTHER new input and save it to the end.
        i++;
    }
    // str[i] = 0;  // missing zero terminator.
}

这是我的写法:

#include <stddef.h>
//#include <stdio.h>

extern unsigned char getchar(void);

// returns length.
// negative means EOF.  TODO: implement an EOF check if your getchar() supports it.
// FIXME: take a max-length arg to make it possible to prevent buffer overflows.
ptrdiff_t gets(char *str) {
    char *start = str;  // optional

    char tmp;  // read chars into a local, and check before assigning anything to *str
    while( (tmp = getchar()) != '\r') {
        // TODO: also check for EOF
        *str++ = tmp;            // classic pointer post-increment idiom
    }
    *str = 0;     // terminate the C string.

    return str - start;  // optional, return the length
}

返回字符串长度而不是将其丢弃在已知函数中总是有用的,这只会使编译器花费几条额外的指令。指针增量简化了寻址模式,节省了代码大小。

(与gcc and clang for 32-bit x86 on Godbolt可以很好地编译,对于x86-16应该非常相似。)

您还可以/代替检查'\n',具体取决于您的getchar实现以及它是否使行尾正常化。请记住,如果您有DOS \r行尾,则在读取\n之后停止操作将使"\r\n"未被读取。

在ISO C中,对于以文本模式打开的文件,getchar()应该只给您'\n'行尾,但是您已经使getchar只是DOS int 21h / AH=1的包装(从标准输入读取字符,带回显)功能。这就是设置实施行为的原因。

asm错误:

# in _getchar:
    mov [bp + 4], al         ; clobber memory you don't own.

这将破坏返回地址上方的内存。 char getchar(void)不带任何参数,因此您的函数不会“拥有”该内存。您的编译器应该期望AL中有返回值。 (并且,如果您认为那是通过引用返回的,不,那只是覆盖指针arg。除非调用者甚至没有传递一个。)

如果您希望getchar能够返回与0xFF字节不同的EOF,请在进行系统调用后将其声明为返回int,并将AH设为零。 (因此,您可以在AX中返回16位-1,或者在AX中返回零扩展的unsigned char(即AL中的值)。


顺便说一句,有一个原因gets() is deprecated,实际上在ISO C11中已删除 :在读取未知长度的输入时不可能防止缓冲区溢出。

您的函数应将大小限制作为第二个参数。


直接编程Arduino的AVR或ARM CPU 可能比在模拟的8086上使用DOS系统调用更容易学习,并且更有用。如果您要这样做,那就没有用了在真实的硬件上还是在模拟器上进行操作。

如果您不熟悉分段,也可以尝试将x86作为您的第一门汇编语言学习,并且您不尝试编写引导加载程序(A20门有很多不可思议的传统东西,可以从实模式切换到保护模式)。 DOS系统调用完全过时,除了维护旧版代码库。学习细节如何不同AH =? / int 21h系统调用的工作原理与COBOL一样有用。如果您要创建旧的引导扇区(而不是EFI),则BIOS int 10h和其他系列则稍微有用一些,但是您不需要这样做就可以学习asm。如果您在Linux,Windows,Mac,* BSD或任何其他版本的用户空间中学习asm,那么以后就可以轻松了解/学习与外界进行通信的其他方式(如果需要),并了解内核的工作方式。

Linux系统调用具有类似的ABI(eax=call number / int 0x80sysentersyscall),但是Linux系统调用或多或少是它的POSIX系统调用。对于了解现实世界的低级编程很有用。

带有sys_read的POSIX TTY行缓冲输入的复杂性与DOS字符读取功能和行尾无意义的复杂性不同,但可以说对于学习更有用。