程序集中的文件大小
File size in assembly
我用 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
我的问题是,如何使用这段代码获取文件长度?我读过有一些获取文件大小的方法,但它们看起来都很复杂,我在想当我读取文件时,我应该能够通过存储我在寄存器中读取的字符数来计算文件大小,但我不是对此非常确定,如果可能的话,那么我不知道如何在 tasm 中做到这一点。同样在数据段中,我需要什么变量来存储文件大小?也许一个代码片段可以帮助我理解这个过程,并提供一些有用的评论它是如何工作的。感谢您的帮助。
关于答案的更新:
所以我尝试将十六进制转换为十进制,它有点管用,但我一定有一些错误,因为它适用于小文件,假设我尝试了 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
显示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
我尝试对代码进行广泛的注释,并使用 "more straightforward" 实现这些东西,避免一些不太常见的指令,并保持逻辑简单,所以实际上你应该能够完全理解它是如何工作的.
我再次强烈建议您使用调试器并逐条指令慢慢地检查它,观察 CPU 状态如何变化,以及它如何与我的评论相关联(注意我想评论的不是什么指令确实如此,因为可以在指令参考指南中找到,但我试图评论我的人为意图,为什么我在那里写它 - 如果出现错误,这会让你知道什么应该是正确的输出错误的代码,以及如何修复它。如果注释只是说明指令的作用,那么您无法说明应该如何修复它)。
现在,如果您要实现 32b_number_to_decimal_ascii 格式化功能,您可以替换此示例的最后部分以获得十进制长度,但是如果没有适当的调试和测试,这对我来说太棘手了,无法从头开始编写。
对于 asm 的新手来说,最简单的合理实现方式可能是 table 每个 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 除数的 table 并通过减法进行除法的提示,但你却选择了 DIV
。这需要将原始 32b 值复杂地拆分成更小的部分,直到可以使用 div bx=10
提取特定数字的程度。像先做filelength/1e5,然后计算32b的余数(0..99999)值,即使在16b中也能实际除以10(99999/10 = 9999(适合16b),余数9).
你好像没明白为什么128k的文件长度需要32位来存储,各种变量的有效范围是多少。 216 = 65536 (= 64ki) ... 在您 运行 遇到问题之前,您的整数可以有多大。 128ki 是两倍 => 16 位是问题。
有趣的事情...正如你写的 "converting from hex to decimal",起初我想:什么,你把那个六进制字符串转换成十进制字符串???但实际上这听起来用 16b 数学是可行的,通过整个六边形数字首先只拾取 100 值(从特定的 k*16n 中提取值),然后在下一次迭代中进行 101 计数等...
但是从我之前的答案中减去 32 位数字的除法应该更容易做到,尤其是理解它是如何工作的。
您在地址 si
处写入了十进制字符串,但我没有看到您如何设置 si
,所以它可能不小心指向了您的 MENU 字符串,并且您覆盖了该内存(再次使用调试器,检查 ds:si
值以查看使用的地址,并使用内存视图查看写入的内存内容会给你提示,问题是什么。
基本上你没有听从我的建议(学习调试并理解我所说的 32b - 32b 循环除法的意思),试图从互联网上复制一些完成的代码,浪费了很多时间。至少看起来你可以更好地将它连接到你自己的代码,但你仍然遗漏了明显的问题,比如没有设置 si
指向十进制字符串的目的地。
也许首先尝试打印文件中的所有数字,并将大小保持为六进制(至少尝试弄清楚,为什么转换为六进制很容易,而转换为十进制则不然)。所以你会完成大部分任务,然后你可以玩最难的部分(16b asm 中的 32b 到十进制)。
顺便说一句,就在大约一天前,有人在 16b 汇编中对 64b 数字进行 addition/subtraction 时遇到问题,所以这个答案可能会给你进一步的提示,为什么要通过 sub/add 循环进行这些转换这不是个坏主意,如果您了解它的工作原理,它就是相当 "simple" 的代码:
我用 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
我的问题是,如何使用这段代码获取文件长度?我读过有一些获取文件大小的方法,但它们看起来都很复杂,我在想当我读取文件时,我应该能够通过存储我在寄存器中读取的字符数来计算文件大小,但我不是对此非常确定,如果可能的话,那么我不知道如何在 tasm 中做到这一点。同样在数据段中,我需要什么变量来存储文件大小?也许一个代码片段可以帮助我理解这个过程,并提供一些有用的评论它是如何工作的。感谢您的帮助。
关于答案的更新:
所以我尝试将十六进制转换为十进制,它有点管用,但我一定有一些错误,因为它适用于小文件,假设我尝试了 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
显示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
我尝试对代码进行广泛的注释,并使用 "more straightforward" 实现这些东西,避免一些不太常见的指令,并保持逻辑简单,所以实际上你应该能够完全理解它是如何工作的.
我再次强烈建议您使用调试器并逐条指令慢慢地检查它,观察 CPU 状态如何变化,以及它如何与我的评论相关联(注意我想评论的不是什么指令确实如此,因为可以在指令参考指南中找到,但我试图评论我的人为意图,为什么我在那里写它 - 如果出现错误,这会让你知道什么应该是正确的输出错误的代码,以及如何修复它。如果注释只是说明指令的作用,那么您无法说明应该如何修复它)。
现在,如果您要实现 32b_number_to_decimal_ascii 格式化功能,您可以替换此示例的最后部分以获得十进制长度,但是如果没有适当的调试和测试,这对我来说太棘手了,无法从头开始编写。
对于 asm 的新手来说,最简单的合理实现方式可能是 table 每个 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 除数的 table 并通过减法进行除法的提示,但你却选择了 DIV
。这需要将原始 32b 值复杂地拆分成更小的部分,直到可以使用 div bx=10
提取特定数字的程度。像先做filelength/1e5,然后计算32b的余数(0..99999)值,即使在16b中也能实际除以10(99999/10 = 9999(适合16b),余数9).
你好像没明白为什么128k的文件长度需要32位来存储,各种变量的有效范围是多少。 216 = 65536 (= 64ki) ... 在您 运行 遇到问题之前,您的整数可以有多大。 128ki 是两倍 => 16 位是问题。
有趣的事情...正如你写的 "converting from hex to decimal",起初我想:什么,你把那个六进制字符串转换成十进制字符串???但实际上这听起来用 16b 数学是可行的,通过整个六边形数字首先只拾取 100 值(从特定的 k*16n 中提取值),然后在下一次迭代中进行 101 计数等...
但是从我之前的答案中减去 32 位数字的除法应该更容易做到,尤其是理解它是如何工作的。
您在地址 si
处写入了十进制字符串,但我没有看到您如何设置 si
,所以它可能不小心指向了您的 MENU 字符串,并且您覆盖了该内存(再次使用调试器,检查 ds:si
值以查看使用的地址,并使用内存视图查看写入的内存内容会给你提示,问题是什么。
基本上你没有听从我的建议(学习调试并理解我所说的 32b - 32b 循环除法的意思),试图从互联网上复制一些完成的代码,浪费了很多时间。至少看起来你可以更好地将它连接到你自己的代码,但你仍然遗漏了明显的问题,比如没有设置 si
指向十进制字符串的目的地。
也许首先尝试打印文件中的所有数字,并将大小保持为六进制(至少尝试弄清楚,为什么转换为六进制很容易,而转换为十进制则不然)。所以你会完成大部分任务,然后你可以玩最难的部分(16b asm 中的 32b 到十进制)。
顺便说一句,就在大约一天前,有人在 16b 汇编中对 64b 数字进行 addition/subtraction 时遇到问题,所以这个答案可能会给你进一步的提示,为什么要通过 sub/add 循环进行这些转换这不是个坏主意,如果您了解它的工作原理,它就是相当 "simple" 的代码: