装配中的文件大小

时间:2017-03-06 12:14:50

标签: file assembly tasm

我在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

1 个答案:

答案 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