程序集中的文件大小

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" 的代码: