我在TASM程序集中编写了以下代码,用于从文件读取并使用缓冲区打印出文件内容。
缓冲区声明:
buffer db 100 dup (?), '$' ;regarding to comment, buffer is db 101 dup (?), '$'
修改
我的程序结构是:
任务1询问我要读取的文件名(字符串)。
输入文件名后,程序task1
打开文件。
mov ah, 3dh
xor al, al
lea dx, fname
int 21h ;open file
jc openError
mov bx, ax
不确定,如果打开文件是正确的,因为我已经看到了打开文件的类似方法,但我这里没有处理程序,或者?
以下是阅读部分task2
:
task2 proc
pam 10,13 ;pam is macro for printing out
read:
mov ah, 3fh
lea dx, buffer
mov cx, 100
int 21h
jc readError ;read error , jump
mov si, ax
mov buffer[si], '$'
mov ah, 09h
int 21h ;print out
cmp si, 100
je read
jmp stop ;end
openError:
pam error1
jmp stop
readError:
pam error2
stop: ret
task2 endp
我的问题是,如何使用此代码获取文件长度?我已经读过有一些获取文件大小的方法,但它们看起来都非常复杂,我在想,当我读取文件时,我应该能够通过存储我在寄存器中读取的字符数来计算文件大小但我不是如此可靠,如果有可能,那么我不知道如何做到这一点。同样在数据段中,我需要什么变量来存储文件大小?也许一个代码片段可以帮助我理解这个过程,并提供一些有用的评论,它是如何工作的。谢谢你的帮助。
关于答案的更新:
所以我尝试将六进制转换为十进制,它有点工作,但我必须有一些错误,因为它适用于小文件,让我说我尝试1kB文件,它工作,我在屏幕上打印出的字节大小,但当我尝试更大的文件,如128kB,十进制数字不正确 - 打印尺寸错误,文件很大130,862 bytes
,我的转换给了我-- MENU653261 = Enter file name
。
... code from the answer ...
lea di,[buffer] ; hexa number will be written to buffer
mov word ptr [di],('0' + 'x'*256) ; with C-like "0x" prefix
add di,2 ; "0x" written at start of buffer
mov ax,dx
call AxTo04Hex ; upper word converted to hexa string
mov ax,cx
call AxTo04Hex ; lower word converted to hexa string
mov byte ptr [di],'$' ; string terminator
;HEX TO DECIMAL = my code starts here
mov cx,0
mov bx,10
loop1: mov dx,0
div bx
add dl,30h
push dx
inc cx
cmp ax,9
jg loop1
add al,30h
mov [si],al
loop2: pop ax
inc si
mov [si],al
loop loop2
; output final string to screen
mov ah,9
lea dx,[buffer]
int 21h
以下是打印出小数值时的外观。它与下一行混合在一起。我试图把它移到下一行,但没有帮助。 screenshot
答案 0 :(得分:1)
显示六位格式长度的DOS文件的简单代码(文件名在源代码中硬编码,将其编辑为现有文件):
.model small
.stack 100h
.data
fname DB "somefile.ext", 0
buffer DB 100 dup (?), '$'
.code
start:
; set up "ds" to point to data segment
mov ax,@data
mov ds,ax
; open file first, to get "file handle"
mov ax,3D00h ; ah = 3Dh (open file), al = 0 (read only mode)
lea dx,[fname] ; ds:dx = pointer to zero terminated file name string
int 21h ; call DOS service
jc fileError
; ax = file handle (16b number)
; now set the DOS internal "file pointer" to the end of opened file
mov bx,ax ; store "file handle" into bx
mov ax,4202h ; ah = 42h, al = 2 (END + cx:dx offset)
xor cx,cx ; cx = 0
xor dx,dx ; dx = 0 (cx:dx = +0 offset)
int 21h ; will set the file pointer to end of file, returns dx:ax
jc fileError ; something went wrong, just exit
; here dx:ax contains length of file (32b number)
; close the file, as we will not need it any more
mov cx,ax ; store lower word of length into cx for the moment
mov ah,3Eh ; ah = 3E (close file), bx is still file handle
int 21h ; close the file
; ignoring any error during closing, so not testing CF here
; BTW, int 21h modifies only the registers specified in documentation
; that's why keeping length in dx:cx registers is enough, avoiding memory/stack
; display dx:cx file length in hexa formatting to screen
; (note: yes, I used dx:cx for storage, not cx:dx as offset for 42h service)
; (note2: hexa formatting, because it's much easier to implement than decimal)
lea di,[buffer] ; hexa number will be written to buffer
mov word ptr [di],('0' + 'x'*256) ; with C-like "0x" prefix
add di,2 ; "0x" written at start of buffer
mov ax,dx
call AxTo04Hex ; upper word converted to hexa string
mov ax,cx
call AxTo04Hex ; lower word converted to hexa string
mov byte ptr [di],'$' ; string terminator
; output final string to screen
mov ah,9
lea dx,[buffer]
int 21h
; exit to DOS with exit code 0 (OK)
mov ax,4C00h
int 21h
fileError:
mov ax,4C01h ; exit with code 1 (error happened)
int 21h
AxTo04Hex: ; subroutine to convert ax into four ASCII hexadecimal digits
; input: ax = 16b value to convert, ds:di = buffer to write characters into
; modifies: di += 4 (points beyond the converted four chars)
push cx ; save original cx to preserve it's value
mov cx,4
AxTo04Hex_singleDigitLoop:
rol ax,4 ; rotate whole ax content by 4 bits "up" (ABCD -> BCDA)
push ax
and al,0Fh ; keep only lowest nibble (4 bits) value (0-15)
add al,'0' ; convert it to ASCII: '0' to '9' and 6 following chars
cmp al,'9' ; if result is '0' to '9', just store it, otherwise fix
jbe AxTo04Hex_notLetter
add al,'A'-(10+'0') ; fix value 10+'0' into 10+'A'-10 (10-15 => 'A' to 'F')
AxTo04Hex_notLetter:
mov [di],al ; write ASCII hexa digit (0-F) to buffer
inc di
pop ax ; restore other bits of ax back for next loop
dec cx ; repeat for all four nibbles
jnz AxTo04Hex_singleDigitLoop
pop cx ; restore original cx value back
ret ; ax is actually back to it's input value here :)
end start
我试图广泛地评论代码,并使用"更直接的"实现这些东西,避免一些不太常见的指令,并保持逻辑简单,所以实际上你应该能够理解它是如何完全工作的。
我再强烈建议你使用调试器,然后慢慢地通过指令进行指令,观察CPU状态如何变化,以及它与我的注释之间的关系(请注意我试图评论指令的确切含义) ,因为这可以在指令参考指南中找到,但我试图评论我的人类意图,为什么我在那里写它 - 如果有一些错误,这会让你知道错误代码的正确输出应该是什么,以及如何解决它。如果评论只是说出了指令的作用,那么你就无法告诉它应该如何修复。
现在,如果你要实现32b_number_to_decimal_ascii格式化函数,你可以替换这个例子的最后一部分来获得十进制长度,但是如果没有适当的调试和测试,这对于我来说从头开始写起来太麻烦。
可能由asm新手合理实现的最简单的方法是为每个32b十进制数字设置32b除数,然后为每个数字执行嵌套循环(可能跳过前导零的存储,或者只是递增指针在打印之前跳过它们,这是更复杂的代码逻辑。)
类似于(类似于C的伪代码,希望显示出这个想法):
divisors dd 1000000000, 100000000, 10000000, ... 10, 1
for (i = 0; i < divisors.length; ++i) {
buffer[i] = '0';
while (divisors[i] <= number) {
number -= divisors[i];
++digit[i];
}
}
digit[i] = '$';
// then printing as
ptr_to_print = buffer;
// eat leading zeroes
while ( '0' == ptr_to_print[0] ) ++ptr_to_print;
// but keep at least one zero, if the number itself was zero
if ('$' == ptr_to_print[0] ) --ptr_to_print;
print_it // dx = ptr_to_print, ah = 9, int 21h
如果你想知道,你如何在16位汇编中减去32位数,那实际上并不那么困难(如32b分区):
; dx:ax = 32b number
; ds:si = pointer to memory to other 32b number (mov si,offset divisors)
sub ax,[si] ; subtract lower word, CF works as "borrow" flag
sbb dx,[si+2] ; subtract high word, using the "borrow" of SUB
; optionally: jc overflow
; you can do that "while (divisors[i] <= number)" above
; by subtracting first, and when overflow -> exit while plus
; add the divisor back (add + adc) (to restore "number")
要点更新的要点:
您不能将十六进制转换为十进制(十六进制字符串存储在buffer
中,您不会从那里加载任何内容)。您将ax中的值转换为十进制。 ax
包含前一个十六进制转换调用的低文件长度。因此,对于长度高达65535(0xFFFF =最大16b无符号整数)的文件,它可能会起作用。对于较长的文件,它不会,因为上面的单词位于dx
,您只需mov dx,0
销毁。
如果您实际保留dx
,则将文件长度除以10
,但对于长度为655360+的文件,它会因分割错误(商的溢出)而崩溃。正如我在上面的回答中所写的那样,在8086上进行32b / 16b分割并非易事,而且我甚至不确定什么是有效的方法。我给你提示使用32b除数表,并通过减法进行除法,但是你去了DIV
。这需要将原始32b值精确分割成较小的部分,直到可以使用div bx=10
来提取特定数字。就像首先执行filelength / 1e5一样,然后计算32b余数(0..99999)值,即使在16b中也可以实际除以10(99999/10 = 9999(适合16b),余数为9)。
看起来你不明白为什么128k文件长度需要存储32位,以及各种类型变量的有效范围是多少。 2 16 = 65536(= 64ki)...在遇到问题之前,你的整数有多大。 128ki是两倍于&gt; 16位是问题。
有趣的事情......正如你写的&#34;从十六进制转换为十进制&#34;,起初我虽然:什么,你把那个六进制字符串转换成十进制字符串???但实际上这对于16b数学来说是可行的,要通过整个六进制数首先仅获取10个 0 值(从特定k * 16 n 值中提取),然后在下一个迭代做10 1 计数等......
但是,通过从我之前的答案中减去32位数来进行划分应该更容易,尤其要理解它是如何工作的。
你在地址si
写下十进制字符串,但我不知道你如何设置si
,所以它可能意外指向你的菜单字符串,而你覆盖那个内存(再次使用调试器,检查ds:si
值以查看使用的是什么地址,并使用内存视图来查看写入的内存内容会给你提示,问题是什么。)
基本上你没有遵循我的建议浪费了很多时间(学习调试并理解32b - 32b循环分区的意思),试图从Internet复制一些完成的代码。至少看起来你可以更好地将它连接到你自己的代码,但你仍然缺少明显的问题,比如没有设置si
指向十进制字符串的目标。
也许尝试首先打印文件中的所有数字,并将大小保持为hexa(至少试着找出,为什么转换为hexa很容易,而不是十进制)。因此,您将完成大部分任务,然后您可以使用最难的部分(16b asm中的32b到十进制)。
顺便说一句,就在前一天左右,有人在16b程序集中对64b数字进行加法/减法有问题,所以这个答案可能会给你进一步的提示,为什么通过sub / add循环进行这些转换并不是一个坏主意,它很简单&#34;代码,如果您了解它是如何工作的:https://stackoverflow.com/a/42645266/4271923