有人能告诉我用十进制格式显示寄存器中的值的 * 纯汇编 * 代码吗?请不要建议使用printf hack,然后用gcc编译。
说明:
我对NASM做了一些研究和实验,发现我可以使用c库中的printf函数打印一个整数。我用愚者编译器编译了目标文件,一切都很正常。
但是,我想要实现的是以十进制形式打印存储在任何寄存器中的值。
我做了一些研究,发现DOS命令行的中断向量021h可以显示字符串和字符,而2或9在ah寄存器中,数据在dx中。
结论:
我找到的例子中没有一个展示了如何在不使用C库的printf的情况下以十进制形式显示寄存器的内容值。有人知道如何在汇编中做到这一点吗?
5条答案
按热度按时间nnsrf1az1#
你需要编写一个二进制到十进制的转换例程,然后用十进制的位数产生“数字字符”来打印。
你必须假设某个东西,某个地方,将在你选择的输出设备上打印一个字符。假设它采用EAX中的字符代码并保留所有寄存器..(如果您没有这样的子例程,则会有一个额外的问题,该问题应该是另一个问题的基础)。
如果你在一个寄存器(比如EAX)中有一个数字的二进制代码(例如,一个0-9的值),你可以把这个值转换成一个字符,方法是把“零”字符的ASCII码加到寄存器中。
然后可以调用print_character来打印数字字符代码。
要输出任意值,您需要提取数字并打印它们。
从根本上说,提取数字需要处理10的幂。处理10的一个幂是最简单的,例如10本身。假设我们有一个除以10的例程,它取EAX中的一个值,在EDX中产生一个商,在EAX中产生一个余数。我把它作为一个练习,让你弄清楚如何实现这样的例程。
然后,一个简单的例程,其正确的想法是为该值可能具有的所有数字产生一个数字。一个32位寄存器存储的值为40亿,因此您可能打印10个数字。因此:
这是可行的......但是以相反的顺序打印数字。哎呀!好吧,我们可以利用下推堆栈来存储产生的数字,然后以相反的顺序弹出它们:
留给读者作为练习:取消前导零。此外,由于我们将数字字符写入内存,因此我们可以将其写入缓冲区,然后打印缓冲区内容,而不是将其写入堆栈。这也是留给读者的一个练习。
l7wslrjt2#
**您需要手动将二进制整数转换为ASCII十进制数字的字符串/数组。**ASCII数字由
'0'
(0x 30)到'9'
(0x 39)范围内的单字节整数表示。http://www.asciitable.com/对于2的幂基数(如十六进制),请参见How to convert a binary integer number to a hex string?在二进制和2的幂基数之间转换允许更多优化和简化,因为每组位分别Map到一个十六进制/八进制数字。
大多数操作系统/环境都没有接受整数并将其转换为十进制的系统调用。在将字节发送到操作系统之前,或者自己将其复制到显存,或者在显存中绘制相应的字体字形之前,您必须自己完成这些操作。
到目前为止,最有效的方法是进行一次处理整个字符串的单个系统调用,因为写入8个字节的系统调用的开销基本上与写入1个字节的开销相同。
这意味着我们需要一个缓冲区,但这并不会增加我们的复杂性。2^32-1只有4294967295,也就是10位十进制数字。我们的缓冲区不需要很大,所以我们可以只使用堆栈。
通常的算法产生数字LSD优先(最低有效位优先)。由于打印顺序是MSD优先,我们可以从缓冲区的末尾开始向后工作。在其他地方打印或复制时,只需跟踪它的起始位置,而不必费心将它放在固定缓冲区的开头。不需要通过push/pop来颠倒任何操作,一开始就把它倒着生产。
gcc/clang做得非常好,using a magic constant multiplier代替
div
有效地除以10。(Godbolt编译器资源管理器用于asm输出)。这个code-review Q&A有一个很好的高效的NASM版本,它将字符串累积到一个8字节的寄存器中,而不是存储到内存中,准备存储在您希望字符串开始的位置,而无需额外的复制。
要处理有符号整数:
对无符号绝对值(
if(val<0) val=-val;
)使用此算法。如果原始输入为负数,则在完成后在末尾添加一个'-'
。例如,-10
对10
运行此算法,生成2个ASCII字节。然后在前面存储一个'-'
,作为字符串的第三个字节。这里有一个简单的注解版NASM,使用
div
(速度慢但代码较短)。只需将寄存器更改为ecx
而不是rcx
,就可以轻松地将其移植到32位模式代码中。但是add rsp,24
将变成add esp, 20
,因为push ecx
只有4个字节,而不是8个字节。(您还应该保存/恢复esi
,以符合通常的32位调用约定,除非您要将其制作成宏或仅供内部使用的函数。)system-call部分是64位Linux特有的。用任何适合您系统的内容替换它,例如,调用VDSO页面以在32位Linux上进行有效的系统调用,或者直接使用
int 0x80
以进行低效的系统调用。请参见calling conventions for 32 and 64-bit system calls on Unix/Linux。或者,请参见rkhb对另一个问题的回答,以了解以同样方式工作的32位int 0x80
版本。如果您只需要字符串而不打印它,
rsi
会指向离开循环后的第一个数字。您可以将它从tmp缓冲区复制到任何您实际需要它的位置的开头。或者如果您直接将它生成到最终目的地(例如传递指针arg),你可以用前导零填充,直到你到达你为它留下的空格的前面。没有简单的方法来找出它有多少位数。除非你总是用零填充到一个固定的宽度。**公共领域。**请随意将此文件复制/粘贴到您正在处理的任何文件中。如果文件损坏,您可以保留两个部分。(如果性能很重要,请查看下面的链接;您将需要一个乘法逆函数,而不是
div
。)下面的代码在一个循环中调用它,倒计时到0(包括0)。
组装和链接
使用
strace
来查看这个程序所做的系统调用只有write()
和exit()
。(另请参见x86标记wiki底部的gdb / debugging提示,以及那里的其他链接。)相关:
*这个的32比特版本,使用
int 0x80
做为结尾的write
系统呼叫。几乎是相同的循环。*使用
printf
-如何在组件NASM中打印数字?有x86-64和i386答案。NASM汇编将输入转换为整数?是另一个方向,string-〉int。
使用AT&T语法将整数打印为字符串,使用Linux系统调用而不是printf -AT&T版本(但针对64位整数)。请参阅该文章以了解更多关于性能的评论,以及
div
与使用mul
的编译器生成代码的基准测试。将2个数字相加,并使用与此非常相似的汇编x86 32位版本打印结果。
这个code-review Q&A使用乘法逆运算,将字符串累加到一个8字节的寄存器中,而不是存储到内存中,准备存储在您希望字符串开始的位置,而不需要额外的复制。
How to convert a binary integer number to a hex string?-2的幂基是特殊的。答案包括标量循环(分支和表查找)和SIMD(SSE 2、SSSE 3、AVX 2和AVX 512,这是令人惊讶的。)
How to print integers really fast博客文章比较了C语言中的一些策略。例如,
x % 100
可以创建更多的ILP(指令级并行),或者使用查找表或更简单的乘法逆运算(只需在有限范围内工作,如本答案所示)将0..99余数分解为2个十进制数字。例如,使用
imul r,r,imm8
/shr r,10
与(x * 103) >> 10
进行运算,如另一个答案所示。rjee0c153#
我想你想把这个值输出到stdout
必须使用system call来执行此操作。系统调用依赖于操作系统。
例如Linux:Linux System Call Table
这个Tutorial中的hello world程序可能会给予你一些见解。
p8ekf7hl4#
无法发表评论,所以我以这种方式发布回复。@伊拉巴克斯特,完美的答案,我只想补充一点,你不需要除以10倍,因为你发布了你将寄存器cx设置为值10。只需除以数字,直到“ax==0”
您还必须存储原始号码中有多少位数。
无论如何,你伊拉巴克斯特帮助了我,只是有几种方法如何优化代码:)
这不仅是关于优化,而且是格式化。当你想打印数字54时,你想打印54而不是0000000054:)
juud5qan5#
1 - 9是1 - 9,在这之后,一定有一些我也不知道的转换,假设你有一个AX中的41 H(EAX),并且您希望打印65,而不是“A”,不做一些服务调用。我认为你需要打印一个6和5的字符表示。必须有一个常数,可以添加到那里。您需要一个模数运算符(无论在汇编中如何操作),并对所有数字执行循环。
不确定,但这是我的猜测。