File size in assembly

血红的双手。 提交于 2019-12-24 06:36:16

问题


I have a following code written in TASM assembly for reading from a file and printing out the file content using a buffer.

Buffer declaration:

buffer         db 100 dup (?), '$'   ;regarding to comment, buffer is db 101 dup (?), '$'

EDIT

The structure of my program is:

Task 1 is asking me for a file name (string) which I want to read. After I input file name, the procedure task1 opens the file.

mov ah, 3dh
xor al, al 
lea dx, fname
int 21h                         ;open file
jc openError                    
mov bx, ax 

Not sure, if opening the file is correct, because I have seen similar ways of opening the file but I do not have a handler here, or?

Here is the reading part 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

My question is, how can I get file length using this code? I have read that there are some ways of getting file size but they all look very complicated and I was thinking that when I read file, I should be able to calculate file size by storing number of characters I read in a register but I am not so sure about it and if it is possible, then I have no idea how to do that in tasm. Also in data segment, what variable do I need for storing file size? Maybe a code snippet would help me understand the process with some helpful comments how does it work. Thanks for help.

UPDATE regarding to the answer:

So I tried to convert hexa to decimal, it kinda works but I must have some bug in there because it works for small file, lets say I tried 1kB file and it worked, I got size in Bytes printed out on screen but when I tried bigger file like 128kB, decimal numbers were not correct - printed size was wrong, file is big exactly 130,862 bytes and my conversion gave me -- 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

Here is a screen how it looks when the decimal value gets printed out. It is mixed with the next line. I tried to move it to the next line but did not help. screenshot


回答1:


A simple code to display hexa-formatted length of DOS file (file name is hardcoded in source, edit it to existing file):

.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

I tried to comment the code extensively, and to use "more straightforward" implementation of this stuff, avoiding some less common instructions, and keep the logic simple, so actually you should be able to comprehend how it works fully.

Again I strongly advise you to use debugger and go instruction by instruction slowly over it, watching how CPU state is changing, and how it correlates with my comments (note I'm trying to comment not what the instruction exactly does, as that can be found in instruction reference guide, but I'm trying to comment my human intention, why I wrote it there - in case of some mistake this gives you idea what should have been the correct output of the wrong code, and how to fix it. If comments just say what the instruction does, then you can't tell how it should be fixed).

Now if you would implement 32b_number_to_decimal_ascii formatting function, you can replace the last part of this example to get length in decimal, but that's too tricky for me to write from head, without proper debugging and testing.

Probably the simplest way which is reasonably to implement by somebody new to asm is to have table with 32b divisors for each 32b decimal digit and then do nested loop for each digits (probably skipping storage of leading zeroes, or just incrementing the pointer before printing to skip over them, that's even less complex logic of code).

Something like (pseudo code similar to C, hopefully showing the idea):

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

And if you wonder, how do you subtract 32 bit numbers in 16 bit assembly, that's actually not that difficult (as 32b division):

; 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")

Points to question update:

You don't convert hex to decimal (hex string is stored in buffer, you don't load anything from there). You convert value in ax to decimal. The ax contains low word of file length from previous hex conversion call. So for files of length up to 65535 (0xFFFF = maximum 16b unsigned integer) it may work. For longer files it will not, as upper word is in dx, which you just destroy by mov dx,0.

If you would actually keep dx as is, you would divide file length by 10, but for file with 655360+ length it would crash on divide error (overflow of quotient). As I wrote in my answer above, doing 32b / 16b division on 8086 is not trivial, and I'm not even sure what is the efficient way. I gave you hint about using table of 32b divisors, and doing the division by subtraction, but you went for DIV instead. That would need some sophisticated split of the original 32b value into smaller parts up to a point where you can use div bx=10 to extract particular digits. Like doing filelength/1e5 first, then calculate 32b remainder (0..99999) value, which can be actually divided by 10 even in 16b (99999/10 = 9999 (fits 16b), remainder 9).

Looks like you didn't understand why 128k file length needs 32 bits to store, and what are the effective ranges of various types of variables. 216 = 65536 (= 64ki) ... that how big your integers can get, before you run into problems. 128ki is two times over that => 16 bit is problem.

Funny thing... as you wrote "converting from hex to decimal", at first I though: what, you convert that hexa string into decimal string??? But actually that sounds doable with 16b math, to go through whole hexa number first picking up only 100 values (extracted from particular k*16n value), then in next iteration doing 101 counting, etc...

But that division by subtracting 32bit numbers from my previous answer should be much easier to do, and especially to comprehend, how it works.

You write the decimal string at address si, but I don't see how you set si, so it's probably pointing into your MENU string by accident, and you overwrite that memory (again using debugger, checking ds:si values to see what address is used, and using memory view to watch the memory content written would give you hint, what is the problem).

Basically you wasted many hours by not following my advices (learning debugging and understanding what I meant by 32b - 32b loop doing division), trying to copy some finished code from Internet. At least it looks like you can somewhat better connect it to your own code, but you are still missing obvious problems, like not setting si to point to destination for decimal string.

Maybe try to first to print all numbers from the file, and keep the size in hexa (at least try to figure out, why conversion to hexa is easy, and to decimal not). So you will have most of the task done, then you can play with the hardest part (32b to decimal in 16b asm).

BTW, just a day ago or so somebody had problem with doing addition/subtraction over 64b numbers in 16b assembly, so this answer may give you further hints, why doing those conversion by sub/add loops is not that bad idea, it's quite "simple" code if you get the idea how it works: https://stackoverflow.com/a/42645266/4271923



来源:https://stackoverflow.com/questions/42625421/file-size-in-assembly

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!