【问题标题】:gets() function using getchar from assembly使用程序集中的 getchar 获取()函数
【发布时间】:2018-09-02 20:25:03
【问题描述】:

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

这是我的自动取款机代码: 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”

【问题讨论】:

  • 看了一眼,gets 中的 while 条件似乎与正文不匹配。这:(*str = getchar()) != 0xD 意味着正文应该是str++

标签: c assembly dos x86-16


【解决方案1】:

您的 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 系统调用完全过时,除了维护遗留代码库。学习的细节怎么不同啊=?? /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 字符读取功能和行尾废话的复杂性,但可以说学习起来更有用。

【讨论】:

  • @Deduplicator:OP 正在使用自定义的getchar() 实现,使用DOS int 21h / AH=1。我不知道该 API 是否有文本与二进制模式选项,或者它对 EOF 有什么作用。不过,好点,我应该返回ptrdiff_t,所以否定可能意味着EOF。
  • @MichaelPetch:这就是我的观点:我不建议首先使用 DOS 或传统 BIOS 来教授 asm,或者教授 8086。具有不透明输入/输出功能的 32 位用户空间 x86由讲师提供(如 Irvine32)似乎更容易,并且不会强迫您学习 16 位寻址模式限制。或者就像我说的,具有不透明的教师提供的 I/O 功能的 AVR 或 ARM asm 也可以工作。 (顺便说一句,当您发表评论时,我已经将那个咆哮移到了答案的底部,而不是顶部。我认为它不需要在那里。)
  • @MichaelPetch:我会将 2018 年学习 DOS 系统调用与 2018 年学习 COBOL 进行比较:是的,仍然有一些生产代码在使用它,但 AFAIK 没有新代码。我认为那已经过时了。无论如何,我已经改写了我的回答,在我看来,这似乎是教 asm 的一种愚蠢方式。我不会为我发布的第一个版本辩护,因为你是对的,因为我不喜欢它并且从未看到使用它的意义(我在 Linux 出现之前没有使用 PC)。不过,在我目前的答案中仍然有很好的帮助。
  • @MichaelPetch:我明白你的意思。我确实已经添加了“除了维护遗留代码库”,我认为这是有人会编写 DOS 代码的唯一原因。还是人们仍然选择 DOS 来开发新的固件更新实用程序之类的东西?他们手工编写 asm 而不是使用编译器/库函数?我想说的是,学习有关它如何发出 EOF 等信号的 API 细节对大多数人来说没有用,因为完全避免 DOS 是一个完全有效的选择,而且我会推荐它,尤其是对于学习 asm 的初学者.
  • @PeterCordes DOS 是健康和活跃的,如果您有一台基于 x86 的计算机,那么该主板可能首先使用 DOS 开发和测试,然后再使用其他操作系统。生产/制造测试也可能使用 DOS。就嵌入式操作系统而言,没有什么能真正触及它,我们终于开始看到收银机和加油站以及其他使用 DOS 的系统,直到最近才使用其他东西(嵌入式窗口,这真是太可怕了)。因此,开发正在缩减,并且可能仅限于未来 x86 计算机的制造。
猜你喜欢
  • 2021-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
  • 2020-06-13
相关资源
最近更新 更多