【问题标题】:File size in assembly汇编中的文件大小
【发布时间】:2017-03-06 12:14:49
【问题描述】:

我用 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 中做到这一点。同样在数据段中,我需要什么变量来存储文件大小?也许一个代码 sn-p 可以帮助我通过一些有用的 cmets 来理解这个过程,它是如何工作的。感谢您的帮助。

关于答案的更新:

所以我尝试将十六进制转换为十进制,它有点工作,但我必须有一些错误,因为它适用于小文件,假设我尝试了 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

【问题讨论】:

  • 可以通过读取文件的内容并计数来计算文件大小,但它非常无效。通过使用某种sys_stat 调用,您可以读取有关文件的不同信息,而无需读取其全部内容(更快)。存储大小的正确类型取决于所使用的文件系统,对于常见的现代文件系统,64 位是最低要求,因此 C/C++ 中的 long long 或 TASM 中的 qword。从您的源代码来看,它看起来像 DOS 程序集,对于 DOS FAT FS 32 位就足够了(DOS 不支持超过 4GiB 大小的文件,许多工具对于 2+GiB 文件已经失败)。
  • 其实在 DOS 下还有另外一种方法,如果你不想进入“FCB”文件服务,而想继续使用“handle”变体,你可以通过@获取文件长度987654322@到文件末尾,并读取返回值。
  • @Ped7g 我只需要读取大约 1Mb 的文件,所以大文件不是问题。这只是某种学校作业。是的,我正在使用 DOS 32 位。所以你建议使用 sys_stat?
  • 是的,但是我在int 21h的描述中没有找到sys_stat-like的服务,我想大部分是通过读取目录“文件”的内容来实现的,所以那会相当一些代码。如果您只想要长度,请使用文件指针技巧(上面的服务描述链接,params al=2,cx:dx=0,dx:ax 将是文件的长度(在调试器中验证我正确理解了服务描述并且它作品:)))。那应该只是几条(~10)条指令。关于最大文件大小,如果您可以以十六进制显示它,那么正确的 uint32 很容易。在 12 月,在 16b asm 中可能有点棘手,但您可以 google...
  • 顺便说一句,您当前的代码正在覆盖内存,如果您在读取 ​​100 个字节后放置 '$',则需要 101 个字节的缓冲区。

标签: file assembly tasm


【解决方案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 状态如何变化,以及它与我的 cmets 的关系(请注意,我试图评论的不是指令的确切作用,因为这可以在指令参考指南中找到,但我试图评论我的人类意图,为什么我在那里写它 - 如果出现一些错误,这会让你知道错误代码的正确输出应该是什么,以及如何修复它。如果 cmets 只是说出指令的作用,那么您无法告诉它应该如何修复)。

现在,如果您要实现 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位来存储,各种变量的有效范围是多少。 216 = 65536 (= 64ki) ... 在遇到问题之前,你的整数可以有多大。 128ki 是这个的两倍 => 16 位是问题。

有趣的事情......当你写“从十六进制转换为十进制”时,起初我虽然:什么,你把那个六进制字符串转换成十进制字符串???但实际上这听起来对 16b 数学是可行的,通过整个六进制数首先只提取 100 值(从特定 k*16n 值中提取),然后在下一个迭代进行 101 计数等...

但是从我之前的答案中减去 32 位数字的除法应该更容易做到,尤其是理解它是如何工作的。

您将十进制字符串写入地址si,但我看不到您如何设置si,因此它可能意外指向您的菜单字符串,并且您覆盖了该内存(再次使用调试器,检查@ 987654335@ 值来查看使用了什么地址,使用内存视图查看写入的内存内容会给你提示,是什么问题)。

基本上你浪费了很多时间,没有遵循我的建议(学习调试和理解我所说的 32b - 32b 循环进行除法的含义),试图从互联网上复制一些完成的代码。至少看起来您可以更好地将其连接到您自己的代码,但您仍然缺少明显的问题,例如未将 si 设置为指向十进制字符串的目标。

也许首先尝试打印文件中的所有数字,并将大小保持为十六进制(至少尝试弄清楚,为什么转换为六进制很容易,而转换为十进制则不是)。所以你将完成大部分任务,然后你可以玩最难的部分(16b asm 中的 32b 到十进制)。

顺便说一句,就在大约一天前,有人在 16b 汇编中对 64b 数字进行加法/减法运算时遇到问题,所以这个答案可能会给您进一步的提示,为什么通过 sub/add 循环进行这些转换并不是那么糟糕的主意,如果您了解它的工作原理,这是非常“简单”的代码:https://stackoverflow.com/a/42645266/4271923

【讨论】:

  • 与某些专业解决方案相比,字符串例程建议的 32b 值可能非常低效(如果您有来自某些 16b C/C++ 编译器的 16b clib,您可以尝试对其进行逆向工程 sprintf 实现看看它如何转换 32b 整数),所以这不是真正要使用的东西,在性能很重要的地方,它被设计为实现起来相当简单,而不是快速。仍然用于打印文件的长度,一旦它肯定会足够快。 :D(但用它打印百万个数字可能需要一些时间)。
  • 老兄,我会喜欢最简单的可能很慢的代码,我不在乎 :D 我必须在 2 周内完成 3 个其他项目,所以我只需要摆脱这个,因为它完全超出我的个人资料 :D 但回到代码。我尝试使用您的代码创建新文件,必须使用ds 而不是@data 来读取数据段,然后AxTo04Hex_singleDigitLoop 缺少:,仅此而已。当我运行程序时,它只给了我两个空行,第三个是Program executed succesfully. 就是这样。
  • 另外,如果文件名必须由用户设置,这段代码将如何改变?所以我可以将名称设置为用户输入...因为这就是我现在所拥有的,而且我使用文件处理程序的打开过程看起来不同,而且有点令人困惑。
  • 关于ds:不知道你做了什么,你是用TASM编译的吗?我将尝试将其修复为准备好用 TASM 编译的完整 .exe 示例。如果您已经有用户输入的文件名,您应该了解哪个部分执行此操作,文件名存储在哪里等等......所以您应该能够连接这两者。 :/听起来您不使用调试器,并且您无法检查自己出了什么问题。这很糟糕,如果您不知道使用调试器,您可能无法完成任务。如果您有 TASM,那么您可能还有 TD(Turbo Debugger)。现在花上 1-2 小时。
  • @Ady96 我终于明白了ds 的事情,即使在你发表评论后我也没有注意到dx,哇...盲人导致盲人。无论如何,我设法找到了 TASM 的人,并验证了代码是否有效,只需将文件名编辑为与 .exe 位于同一目录中的某个文件,或者将完整路径放在那里,如“F:\DOWNLOADS\ARCHIVEM~1.RAR " -> 我尝试了一些 1GB 的文件,它确实输出了它的十六进制长度。 ...如果它对您不起作用,请尝试 TD 并找出为什么它没有到达结尾 ah=9、int 21h 或那里打印的内容。
猜你喜欢
  • 2014-03-06
  • 1970-01-01
  • 1970-01-01
  • 2016-07-22
  • 1970-01-01
  • 1970-01-01
  • 2015-06-09
  • 1970-01-01
相关资源
最近更新 更多