forked from KolibriOS/kolibrios
766 lines
24 KiB
NASM
766 lines
24 KiB
NASM
|
; This program parses .fas file generated by fasm
|
||
|
; and prints the list of dependencies for make.
|
||
|
; Usage: fasmdep [-e] [<input file> [<output file>]].
|
||
|
; If input file is not given, the program reads from stdin.
|
||
|
; If output file is not given, the program writes to stdout.
|
||
|
; If the option -e is given, the program also creates an empty
|
||
|
; goal for every dependency, this prevents make errors when
|
||
|
; files are renamed.
|
||
|
|
||
|
; This definition controls the choice of platform-specific code.
|
||
|
;define OS WINDOWS
|
||
|
define OS LINUX
|
||
|
|
||
|
; Program header.
|
||
|
match =WINDOWS,OS { include 'windows_header.inc' }
|
||
|
match =LINUX,OS { include 'linux_header.inc' }
|
||
|
|
||
|
include 'fas.inc'
|
||
|
|
||
|
; Main code
|
||
|
start:
|
||
|
; 1. Setup stack frame, zero-initialize all local variables.
|
||
|
virtual at ebp - .localsize
|
||
|
.begin:
|
||
|
.flags dd ? ; 1 if '-e' is given
|
||
|
.in dd ? ; handle of input file
|
||
|
.out dd ? ; handle of output file
|
||
|
.buf dd ? ; pointer to data of .fas file
|
||
|
.allocated dd ? ; number of bytes allocated for .buf
|
||
|
.free dd ? ; number of bytes free in .buf
|
||
|
.outstart dd ? ; offset in .buf for start of data to output
|
||
|
.names dd ? ; offset in .buf for start of output names
|
||
|
.include dd ? ; offset in .buf to value of %INCLUDE%
|
||
|
.testname dd ? ; offset in .buf for start of current file name
|
||
|
.prevfile dd ? ; offset in .buf for previous included file name
|
||
|
.prevfilefrom dd ? ; offset in .buf for .asm/.inc for .prevfile
|
||
|
.localsize = $ - .begin
|
||
|
match =LINUX,OS {
|
||
|
.argc dd ?
|
||
|
.argv:
|
||
|
}
|
||
|
end virtual
|
||
|
|
||
|
mov ebp, esp
|
||
|
xor eax, eax
|
||
|
repeat .localsize / 4
|
||
|
push eax
|
||
|
end repeat
|
||
|
; 2. Call the parser of the command line.
|
||
|
call get_params
|
||
|
; 3. Load the input file.
|
||
|
; Note that stdin can be a pipe,
|
||
|
; useful in bash-construction "fasm input.asm -s >(fasmdep > input.Po)",
|
||
|
; so its size is not always known in the beginning.
|
||
|
; So, we must read input file in portions, reallocating memory if needed.
|
||
|
.readloop:
|
||
|
; 3a. If the size is less than 32768 bytes, reallocate buffer.
|
||
|
; Otherwise, goto 3c.
|
||
|
cmp [.free], 32768
|
||
|
jae .norealloc
|
||
|
; 3b. Reallocate buffer.
|
||
|
; Start with 65536 bytes and then double the size every time.
|
||
|
mov eax, 65536
|
||
|
mov ecx, [.allocated]
|
||
|
add ecx, ecx
|
||
|
jz @f
|
||
|
mov eax, ecx
|
||
|
@@:
|
||
|
call realloc
|
||
|
.norealloc:
|
||
|
; 3c. Read the next portion.
|
||
|
call read
|
||
|
sub [.free], eax
|
||
|
test eax, eax
|
||
|
jnz .readloop
|
||
|
; 4. Sanity checks.
|
||
|
; We don't use .section_* and .symref_*, so allow them to be absent.
|
||
|
mov edi, [.allocated]
|
||
|
sub edi, [.free]
|
||
|
; Note that edi = number of bytes which were read
|
||
|
; and simultaneously pointer to free space in the buffer relative to [.buf].
|
||
|
cmp edi, fas_header.section_offs
|
||
|
jb badfile
|
||
|
mov ebx, [.buf]
|
||
|
cmp [ebx+fas_header.signature], FAS_SIGNATURE
|
||
|
jnz badfile
|
||
|
cmp [ebx+fas_header.headersize], fas_header.section_offs
|
||
|
jb badfile
|
||
|
; 5. Get %INCLUDE% environment variable, it will be useful.
|
||
|
mov [.include], edi
|
||
|
mov esi, include_variable
|
||
|
sub esi, ebx
|
||
|
call get_environment_variable
|
||
|
; 6. Start writing dependencies: copy output and input files.
|
||
|
mov [.outstart], edi
|
||
|
; 6a. Copy output name.
|
||
|
mov esi, [ebx+fas_header.output]
|
||
|
call copy_asciiz_escaped
|
||
|
; 6b. Write ": ".
|
||
|
stdcall alloc_in_buf, 2
|
||
|
mov word [edi+ebx], ': '
|
||
|
inc edi
|
||
|
inc edi
|
||
|
; 6c. Copy input name.
|
||
|
mov [.names], edi
|
||
|
mov esi, [ebx+fas_header.input]
|
||
|
call copy_asciiz_escaped
|
||
|
; 7. Scan for 'include' dependencies.
|
||
|
; 7a. Get range for scanning.
|
||
|
mov edx, [ebx+fas_header.preproc_size]
|
||
|
mov esi, [ebx+fas_header.preproc_offs]
|
||
|
add edx, esi
|
||
|
.include_loop:
|
||
|
; 7b. Loop over preprocessed lines in the range.
|
||
|
cmp esi, edx
|
||
|
jae .include_done
|
||
|
; 7c. For every line, ignore header and do internal loop over tokens.
|
||
|
add esi, preproc_line_header.contents
|
||
|
.include_loop_int:
|
||
|
; There are five types of tokens:
|
||
|
; 1) "start preprocessor data" with code ';' <byte length> <length*byte token>
|
||
|
; 2) "quoted string" with code '"' <dword length> <length*byte string>
|
||
|
; 3) "word" with code 1Ah <byte length> <length*byte word>
|
||
|
; 4) one-byte tokens like "+", "(" and so on
|
||
|
; 5) "end-of-line" token with code 0.
|
||
|
mov al, [esi+ebx]
|
||
|
inc esi
|
||
|
; 7d. First, check token type parsing the first byte.
|
||
|
; For preprocessor tokens, continue to 7e.
|
||
|
; For quoted strings, go to 7g.
|
||
|
; For words, go to 7h.
|
||
|
; For end-of-line, exit the internal loop, continue the external loop.
|
||
|
; Otherwise, just continue the internal loop.
|
||
|
cmp al, ';'
|
||
|
jnz .notprep
|
||
|
; 7e. For "include" tokens length=7, token is "include", the next token is
|
||
|
; quoted string.
|
||
|
; These tokens are handled in 7f, other preprocessor tokens in 7g.
|
||
|
cmp byte [esi+ebx], 7
|
||
|
jnz .notinclude
|
||
|
cmp dword [esi+ebx+1], 'incl'
|
||
|
jnz .notinclude
|
||
|
cmp dword [esi+ebx+5], 'ude"'
|
||
|
jnz .notinclude
|
||
|
; 7f. Skip over end of this token and the type byte of the next token;
|
||
|
; this gives the pointer to length-prefixed string, which should be added
|
||
|
; to dependencies. Note that doing that skips that string,
|
||
|
; so after copying just continue the internal loop.
|
||
|
add esi, 9
|
||
|
call add_separator
|
||
|
call copy_name_escaped
|
||
|
jmp .include_loop_int
|
||
|
.quoted:
|
||
|
; 7g. To skip a word, load the dword length and add it to pointer.
|
||
|
add esi, [esi+ebx]
|
||
|
add esi, 4
|
||
|
jmp .include_loop_int
|
||
|
.notinclude:
|
||
|
; 7h. To skip this token, load the byte length and add it to pointer.
|
||
|
; Note that word tokens and preprocessor tokens have a similar structure,
|
||
|
; so both are handled here.
|
||
|
movzx eax, byte [esi+ebx]
|
||
|
lea esi, [esi+eax+1]
|
||
|
jmp .include_loop_int
|
||
|
.notprep:
|
||
|
cmp al, 1Ah
|
||
|
jz .notinclude
|
||
|
cmp al, '"'
|
||
|
jz .quoted
|
||
|
test al, al
|
||
|
jnz .include_loop_int
|
||
|
jmp .include_loop
|
||
|
.include_done:
|
||
|
; 8. Scan for 'file' dependencies.
|
||
|
; 8a. Get range for scanning.
|
||
|
; Note that fas_header.asm_size can be slighly greater than the
|
||
|
; real size, so we break from the loop when there is no space left
|
||
|
; for the entire asm_row structure, not when .asm_size is exactly reached.
|
||
|
mov esi, [ebx+fas_header.asm_offs]
|
||
|
mov edx, [ebx+fas_header.asm_size]
|
||
|
add edx, esi
|
||
|
sub edx, asm_row.sizeof
|
||
|
push edx
|
||
|
jc .file_done
|
||
|
.file_loop:
|
||
|
; 8b. Loop over assembled lines in the range.
|
||
|
cmp esi, [esp]
|
||
|
ja .file_done
|
||
|
; 8c. For every assembled line, look at first token;
|
||
|
; go to 8d for token 'file',
|
||
|
; go to 8e for token 'format',
|
||
|
; go to 8f for token 'section',
|
||
|
; continue the loop otherwise.
|
||
|
mov eax, [esi+ebx+asm_row.preproc_offs]
|
||
|
add eax, [ebx+fas_header.preproc_offs]
|
||
|
cmp byte [eax+ebx+preproc_line_header.contents], 1Ah
|
||
|
jnz .file_next
|
||
|
movzx ecx, byte [eax+ebx+preproc_line_header.contents+1]
|
||
|
cmp cl, 4
|
||
|
jnz .file_no_file
|
||
|
cmp dword [eax+ebx+preproc_line_header.contents+2], 'file'
|
||
|
jnz .file_no_file4
|
||
|
; 8d. For lines starting with 'file' token, loop over tokens and get names.
|
||
|
; Note that there can be several names in one line.
|
||
|
; Parsing of tokens is similar to step 5 with the difference that
|
||
|
; preprocessor token stops processing: 'file' directives are processed
|
||
|
; in assembler stage.
|
||
|
; We save/restore esi since it is used for another thing in the internal loop;
|
||
|
; we push eax, which currently contains ebx-relative pointer to
|
||
|
; preproc_line_header, to be able to access it from resolve_name.
|
||
|
push esi eax
|
||
|
lea esi, [eax+preproc_line_header.contents+6]
|
||
|
.file_loop_int:
|
||
|
mov al, [esi+ebx]
|
||
|
inc esi
|
||
|
test al, al
|
||
|
jz .file_loop_int_done
|
||
|
cmp al, ';'
|
||
|
jz .file_loop_int_done
|
||
|
cmp al, 1Ah
|
||
|
jz .fileword
|
||
|
cmp al, '"'
|
||
|
jnz .file_loop_int
|
||
|
call resolve_name
|
||
|
add esi, [esi+ebx-4]
|
||
|
jmp .file_loop_int
|
||
|
.fileword:
|
||
|
movzx eax, byte [esi+ebx]
|
||
|
lea esi, [esi+eax+1]
|
||
|
jmp .file_loop_int
|
||
|
.file_loop_int_done:
|
||
|
pop eax esi
|
||
|
jmp .file_next
|
||
|
.file_no_file4:
|
||
|
cmp dword [eax+ebx+preproc_line_header.contents+2], 'data'
|
||
|
jnz .file_next
|
||
|
jmp .file_scan_from
|
||
|
.file_no_file:
|
||
|
cmp cl, 6
|
||
|
jnz .file_no_format
|
||
|
cmp dword [eax+ebx+preproc_line_header.contents+2], 'form'
|
||
|
jnz .file_no_format
|
||
|
cmp word [eax+ebx+preproc_line_header.contents+6], 'at'
|
||
|
jnz .file_no_format
|
||
|
; 8e. For lines starting with 'format' token, loop over tokens and look for stub name.
|
||
|
; Note that there can be another quoted string, namely file extension;
|
||
|
; stub name always follows the token 'on'.
|
||
|
mov edx, TokenOn
|
||
|
call scan_after_word
|
||
|
jmp .file_next
|
||
|
.file_no_format:
|
||
|
cmp cl, 7
|
||
|
jnz .file_no_section
|
||
|
cmp dword [eax+ebx+preproc_line_header.contents+2], 'sect'
|
||
|
jnz .file_no_section
|
||
|
mov edx, dword [eax+ebx+preproc_line_header.contents+6]
|
||
|
and edx, 0FFFFFFh
|
||
|
cmp edx, 'ion'
|
||
|
jnz .file_no_section
|
||
|
.file_scan_from:
|
||
|
mov edx, TokenFrom
|
||
|
call scan_after_word
|
||
|
.file_no_section:
|
||
|
.file_next:
|
||
|
add esi, asm_row.sizeof
|
||
|
jmp .file_loop
|
||
|
.file_done:
|
||
|
pop edx
|
||
|
; 9. Write result.
|
||
|
; 9a. Append two newlines to the end of buffer.
|
||
|
stdcall alloc_in_buf, 2
|
||
|
mov word [edi+ebx], 10 * 101h
|
||
|
inc edi
|
||
|
inc edi
|
||
|
; 9b. If '-e' option was given, duplicate dependencies list as fake goals
|
||
|
; = copy all of them and append ":\n\n".
|
||
|
cmp [.flags], 0
|
||
|
jz .nodup
|
||
|
lea ecx, [edi+1]
|
||
|
mov esi, [.names]
|
||
|
sub ecx, esi
|
||
|
stdcall alloc_in_buf, ecx
|
||
|
add esi, ebx
|
||
|
add edi, ebx
|
||
|
rep movsb
|
||
|
mov byte [edi-3], ':'
|
||
|
mov word [edi-2], 10 * 101h
|
||
|
sub edi, ebx
|
||
|
.nodup:
|
||
|
; 9c. Write to output file.
|
||
|
mov eax, [.outstart]
|
||
|
sub edi, eax
|
||
|
add eax, ebx
|
||
|
call write
|
||
|
; 10. Exit.
|
||
|
xor eax, eax
|
||
|
call exit
|
||
|
|
||
|
; Helper procedure for steps 8e and 8f of main algorithm.
|
||
|
; Looks for quoted strings after given word in edx.
|
||
|
scan_after_word:
|
||
|
push esi eax
|
||
|
lea esi, [eax+preproc_line_header.contents+2+ecx]
|
||
|
.loop:
|
||
|
xor ecx, ecx
|
||
|
.loop_afterword:
|
||
|
mov al, [esi+ebx]
|
||
|
inc esi
|
||
|
test al, al
|
||
|
jz .loop_done
|
||
|
cmp al, ';'
|
||
|
jz .loop_done
|
||
|
cmp al, 1Ah
|
||
|
jz .word
|
||
|
cmp al, '"'
|
||
|
jnz .loop
|
||
|
test ecx, ecx
|
||
|
jz .skip_quoted
|
||
|
call resolve_name
|
||
|
.loop_done:
|
||
|
pop eax esi
|
||
|
ret
|
||
|
.skip_quoted:
|
||
|
add esi, [esi+ebx]
|
||
|
add esi, 4
|
||
|
jmp .loop
|
||
|
.word:
|
||
|
movzx ecx, byte [esi+ebx]
|
||
|
lea esi, [esi+ecx+1]
|
||
|
cmp cl, byte [edx]
|
||
|
jnz .loop
|
||
|
push esi edi
|
||
|
add esi, ebx
|
||
|
sub esi, ecx
|
||
|
lea edi, [edx+1]
|
||
|
repz cmpsb
|
||
|
pop edi esi
|
||
|
jnz .loop
|
||
|
dec ecx
|
||
|
jmp .loop_afterword
|
||
|
|
||
|
; Helper procedure for step 6 of the main procedure.
|
||
|
; Copies the ASCIIZ name from strings section to the buffer.
|
||
|
copy_asciiz_escaped:
|
||
|
add esi, [ebx+fas_header.strings_offs]
|
||
|
.loop:
|
||
|
mov al, [esi+ebx]
|
||
|
test al, al
|
||
|
jz .nothing
|
||
|
call copy_char_escaped
|
||
|
jmp .loop
|
||
|
.nothing:
|
||
|
ret
|
||
|
|
||
|
; Helper procedure for step 7 of the main procedure.
|
||
|
; Copies the name with known length to the buffer.
|
||
|
copy_name_escaped:
|
||
|
mov ecx, [esi+ebx]
|
||
|
add esi, 4
|
||
|
test ecx, ecx
|
||
|
jz .nothing
|
||
|
push ecx
|
||
|
.loop:
|
||
|
mov al, [esi+ebx]
|
||
|
call copy_char_escaped
|
||
|
dec dword [esp]
|
||
|
jnz .loop
|
||
|
pop ecx
|
||
|
.nothing:
|
||
|
ret
|
||
|
|
||
|
; Helper procedure for steps 7 and 8 of the main procedure.
|
||
|
; Writes separator of file names in output = " \\\n".
|
||
|
add_separator:
|
||
|
stdcall alloc_in_buf, 3
|
||
|
mov word [edi+ebx], ' \'
|
||
|
mov byte [edi+ebx+2], 10
|
||
|
add edi, 3
|
||
|
ret
|
||
|
|
||
|
; Helper procedure for step 7 of the main procedure.
|
||
|
; Resolves the path to 'file' dependency and copies
|
||
|
; the full name to the buffer.
|
||
|
resolve_name:
|
||
|
; FASM uses the following order to search for referenced files:
|
||
|
; * path of currently assembling file, which may be .asm or .inc
|
||
|
; * paths from %INCLUDE% for versions >= 1.70
|
||
|
; * current directory = file name is taken as is, without prepending dir name
|
||
|
; We mirror this behaviour, trying to find an existing file somewhere.
|
||
|
; There can be following reasons for the file can not be found anywhere:
|
||
|
; * it has been deleted between compilation and our actions
|
||
|
; * it didn't exist at all, compilation has failed
|
||
|
; * we are running in environment different from fasm environment.
|
||
|
; Assume that the last reason is most probable and that invalid dependency
|
||
|
; is better than absent dependency (it is easier to fix an explicit error
|
||
|
; than a silent one) and output file name without prepending dir name,
|
||
|
; as in the last case. Actually, we even don't need to test existence
|
||
|
; of the file in the current directory.
|
||
|
add esi, 4 ; skip string length
|
||
|
; 1. Get ebx-relative pointer to preproc_line_header, see the comment in start.7d
|
||
|
mov eax, [esp+4]
|
||
|
; 2. Get the path to currently processing file.
|
||
|
push esi
|
||
|
.getpath:
|
||
|
test byte [eax+ebx+preproc_line_header.line_number+3], 80h
|
||
|
jz @f
|
||
|
mov eax, [eax+ebx+preproc_line_header.line_offset]
|
||
|
add eax, [ebx+fas_header.preproc_offs]
|
||
|
jmp .getpath
|
||
|
@@:
|
||
|
mov edx, [eax+ebx+preproc_line_header.source_name]
|
||
|
test edx, edx
|
||
|
jz .frommain
|
||
|
add edx, [ebx+fas_header.preproc_offs]
|
||
|
jmp @f
|
||
|
.frommain:
|
||
|
mov edx, [ebx+fas_header.input]
|
||
|
add edx, [ebx+fas_header.strings_offs]
|
||
|
@@:
|
||
|
; 3. Check that it is not a duplicate of the previous dependency.
|
||
|
; 3a. Compare preprocessor units.
|
||
|
cmp edx, [start.prevfilefrom]
|
||
|
jnz .nodup
|
||
|
; 3b. Compare string lengths.
|
||
|
mov eax, [start.prevfile]
|
||
|
mov ecx, [eax+ebx-4]
|
||
|
cmp ecx, [esi+ebx-4]
|
||
|
jnz .nodup
|
||
|
; 3c. Compare string contents.
|
||
|
push esi edi
|
||
|
lea edi, [eax+ebx]
|
||
|
add esi, ebx
|
||
|
rep cmpsb
|
||
|
pop edi esi
|
||
|
jnz .nodup
|
||
|
; 3d. It is duplicate, just return.
|
||
|
pop esi
|
||
|
ret
|
||
|
.nodup:
|
||
|
; 3e. It is not duplicate. Output separator.
|
||
|
mov [start.prevfilefrom], edx
|
||
|
mov [start.prevfile], esi
|
||
|
call add_separator
|
||
|
; 4. Cut the last component of the path found in step 2.
|
||
|
mov ecx, edx
|
||
|
mov esi, edx
|
||
|
.scanpath:
|
||
|
mov al, [edx+ebx]
|
||
|
test al, al
|
||
|
jz .scandone
|
||
|
cmp al, '/'
|
||
|
jz .slash
|
||
|
cmp al, '\'
|
||
|
jnz .scannext
|
||
|
.slash:
|
||
|
lea ecx, [edx+1]
|
||
|
.scannext:
|
||
|
inc edx
|
||
|
jmp .scanpath
|
||
|
.scandone:
|
||
|
sub ecx, esi
|
||
|
; 5. Try path found in step 4. If found, go to step 8.
|
||
|
mov [start.testname], edi
|
||
|
stdcall copy_string, esi, ecx
|
||
|
pop esi
|
||
|
call expand_environment
|
||
|
call test_file_exists
|
||
|
test eax, eax
|
||
|
jns .found
|
||
|
call revert_testname
|
||
|
; 6. Try each of include paths. For every path, if file is found, go to step 8.
|
||
|
; Otherwise, continue loop over include path.
|
||
|
; Skip this step before 1.70.
|
||
|
cmp [ebx+fas_header.major], 1
|
||
|
ja .checkenv
|
||
|
jb .nocheckenv
|
||
|
cmp [ebx+fas_header.minor], 70
|
||
|
jb .nocheckenv
|
||
|
.checkenv:
|
||
|
mov ecx, [start.include]
|
||
|
.includeloop_ext:
|
||
|
mov eax, ecx
|
||
|
.includeloop_int:
|
||
|
cmp byte [eax+ebx], 0
|
||
|
jz @f
|
||
|
cmp byte [eax+ebx], ';'
|
||
|
jz @f
|
||
|
inc eax
|
||
|
jmp .includeloop_int
|
||
|
@@:
|
||
|
push eax
|
||
|
sub eax, ecx
|
||
|
jz @f
|
||
|
stdcall copy_string, ecx, eax
|
||
|
cmp byte [edi+ebx-1], '/'
|
||
|
jz .hasslash
|
||
|
@@:
|
||
|
stdcall alloc_in_buf, 1
|
||
|
mov byte [edi+ebx], '/'
|
||
|
inc edi
|
||
|
.hasslash:
|
||
|
call expand_environment
|
||
|
call test_file_exists
|
||
|
pop ecx
|
||
|
test eax, eax
|
||
|
jns .found
|
||
|
call revert_testname
|
||
|
cmp byte [ecx+ebx], 0
|
||
|
jz .notfound
|
||
|
inc ecx
|
||
|
cmp byte [ecx+ebx], 0
|
||
|
jnz .includeloop_ext
|
||
|
.nocheckenv:
|
||
|
.notfound:
|
||
|
; 7. File not found neither near the current preprocessor unit nor in %INCLUDE%.
|
||
|
; Assume that it is in the current directory.
|
||
|
call expand_environment
|
||
|
.found:
|
||
|
; 8. Currently we have file name from [start.testname] to edi;
|
||
|
; it is zero-terminated and not space-escaped. Fix both issues.
|
||
|
dec edi
|
||
|
inc [start.free]
|
||
|
push esi
|
||
|
mov edx, [start.testname]
|
||
|
.escapeloop:
|
||
|
cmp edx, edi
|
||
|
jae .escapedone
|
||
|
cmp byte [edx+ebx], ' '
|
||
|
jnz .noescape
|
||
|
stdcall alloc_in_buf, 1
|
||
|
mov ecx, edi
|
||
|
sub ecx, edx
|
||
|
push edi
|
||
|
add edi, ebx
|
||
|
lea esi, [edi-1]
|
||
|
std
|
||
|
rep movsb
|
||
|
cld
|
||
|
pop edi
|
||
|
inc edi
|
||
|
mov byte [edx+ebx], '\'
|
||
|
inc edx
|
||
|
.noescape:
|
||
|
inc edx
|
||
|
jmp .escapeloop
|
||
|
.escapedone:
|
||
|
pop esi
|
||
|
ret
|
||
|
|
||
|
; Helper procedure for resolve_name.
|
||
|
; Allocates space in the buffer and appends the given string to the buffer.
|
||
|
copy_string:
|
||
|
mov eax, [esp+8]
|
||
|
test eax, eax
|
||
|
jz .nothing
|
||
|
stdcall alloc_in_buf, eax
|
||
|
mov ecx, [esp+4]
|
||
|
.copy:
|
||
|
mov al, [ecx+ebx]
|
||
|
inc ecx
|
||
|
cmp al, '\'
|
||
|
jnz @f
|
||
|
mov al, '/'
|
||
|
@@:
|
||
|
mov [edi+ebx], al
|
||
|
inc edi
|
||
|
dec dword [esp+8]
|
||
|
jnz .copy
|
||
|
.nothing:
|
||
|
ret 8
|
||
|
|
||
|
; Helper procedure for resolve_name. Undoes appending of last file name.
|
||
|
revert_testname:
|
||
|
add [start.free], edi
|
||
|
mov edi, [start.testname]
|
||
|
sub [start.free], edi
|
||
|
ret
|
||
|
|
||
|
; Helper procedure for resolve_name. Copies string from esi to edi,
|
||
|
; expanding environment variables.
|
||
|
expand_environment:
|
||
|
; 1. Save esi to restore it in the end of function.
|
||
|
push esi
|
||
|
; 2. Push string length to the stack to be used as a variable.
|
||
|
pushd [esi+ebx-4]
|
||
|
; 3. Scan loop.
|
||
|
.scan:
|
||
|
; 3a. Scan for '%' sign.
|
||
|
call find_percent
|
||
|
.justcopy:
|
||
|
; 3b. Copy the part from the beginning of current portion to '%' sign,
|
||
|
; advance pointer to '%' sign, or end-of-string if no '%' found.
|
||
|
push eax
|
||
|
sub eax, esi
|
||
|
stdcall copy_string, esi, eax
|
||
|
pop esi
|
||
|
; 3c. If string has ended, break from the loop.
|
||
|
cmp dword [esp], 0
|
||
|
jz .scandone
|
||
|
; 3d. Advance over '%' sign.
|
||
|
inc esi
|
||
|
dec dword [esp]
|
||
|
; 3e. Find paired '%'.
|
||
|
call find_percent
|
||
|
; 3f. If there is no paired '%', just return to 3b and copy remaining data,
|
||
|
; including skipped '%'; after that, 3c would break from the loop.
|
||
|
dec esi
|
||
|
cmp dword [esp], 0
|
||
|
jz .justcopy
|
||
|
; 3g. Otherwise, get the value of environment variable.
|
||
|
; Since get_environment_variable requires zero-terminated string
|
||
|
; and returns zero-terminated string, temporarily overwrite trailing '%'
|
||
|
; and ignore last byte in returned string.
|
||
|
; Also convert any backslashes to forward slashes.
|
||
|
inc esi
|
||
|
mov byte [eax+ebx], 0
|
||
|
push eax
|
||
|
push edi
|
||
|
call get_environment_variable
|
||
|
dec edi
|
||
|
inc [start.free]
|
||
|
pop eax
|
||
|
.replaceslash:
|
||
|
cmp eax, edi
|
||
|
jz .replaceslash_done
|
||
|
cmp byte [eax+ebx], '\'
|
||
|
jnz @f
|
||
|
mov byte [eax+ebx], '/'
|
||
|
@@:
|
||
|
inc eax
|
||
|
jmp .replaceslash
|
||
|
.replaceslash_done:
|
||
|
pop esi
|
||
|
mov byte [esi+ebx], '%'
|
||
|
; 3h. Advance over trailing '%'.
|
||
|
inc esi
|
||
|
dec dword [esp]
|
||
|
; 3i. Continue the loop.
|
||
|
jmp .scan
|
||
|
.scandone:
|
||
|
; 4. Zero-terminate resulting string.
|
||
|
stdcall alloc_in_buf, 1
|
||
|
mov byte [edi+ebx], 0
|
||
|
inc edi
|
||
|
; 5. Pop stack variable initialized in step 2.
|
||
|
pop eax
|
||
|
; 6. Restore esi saved in step 1 and return.
|
||
|
pop esi
|
||
|
ret
|
||
|
|
||
|
; Helper procedure for expand_environment.
|
||
|
; Scans the string in esi with length [esp+4]
|
||
|
; until '%' is found or line ended.
|
||
|
find_percent:
|
||
|
mov eax, esi
|
||
|
cmp dword [esp+4], 0
|
||
|
jz .nothing
|
||
|
.scan:
|
||
|
cmp byte [eax+ebx], '%'
|
||
|
jz .nothing
|
||
|
inc eax
|
||
|
dec dword [esp+4]
|
||
|
jnz .scan
|
||
|
.nothing:
|
||
|
ret
|
||
|
|
||
|
; Helper procedure for copy_{name,asciiz}_escaped.
|
||
|
; Allocates space and writes one character, possibly escaped.
|
||
|
copy_char_escaped:
|
||
|
cmp al, ' '
|
||
|
jnz .noescape
|
||
|
stdcall alloc_in_buf, 1
|
||
|
mov byte [edi+ebx], '\'
|
||
|
inc edi
|
||
|
.noescape:
|
||
|
stdcall alloc_in_buf, 1
|
||
|
mov al, [esi+ebx]
|
||
|
inc esi
|
||
|
cmp al, '\'
|
||
|
jnz @f
|
||
|
mov al, '/'
|
||
|
@@:
|
||
|
mov [edi+ebx], al
|
||
|
inc edi
|
||
|
ret
|
||
|
|
||
|
; Helper procedure for ensuring that there is at least [esp+4]
|
||
|
; free bytes in the buffer.
|
||
|
alloc_in_buf:
|
||
|
mov eax, [esp+4]
|
||
|
sub [start.free], eax
|
||
|
jb .need_realloc
|
||
|
ret 4
|
||
|
.need_realloc:
|
||
|
mov eax, [start.allocated]
|
||
|
add eax, eax
|
||
|
push ecx edx
|
||
|
call realloc
|
||
|
pop edx ecx
|
||
|
cmp [start.free], 0
|
||
|
jl .need_realloc
|
||
|
mov ebx, [start.buf]
|
||
|
ret 4
|
||
|
|
||
|
badfile:
|
||
|
mov esi, badfile_string
|
||
|
call sayerr
|
||
|
mov al, 1
|
||
|
call exit
|
||
|
|
||
|
information:
|
||
|
mov esi, information_string
|
||
|
call sayerr
|
||
|
mov al, 2
|
||
|
call exit
|
||
|
|
||
|
nomemory:
|
||
|
mov esi, nomemory_string
|
||
|
call sayerr
|
||
|
mov al, 3
|
||
|
call exit
|
||
|
|
||
|
in_openerr:
|
||
|
mov esi, in_openerr_string
|
||
|
jmp in_err
|
||
|
readerr:
|
||
|
mov esi, readerr_string
|
||
|
in_err:
|
||
|
call sayerr
|
||
|
mov al, 4
|
||
|
call exit
|
||
|
|
||
|
out_openerr:
|
||
|
mov esi, out_openerr_string
|
||
|
jmp out_err
|
||
|
writeerr:
|
||
|
mov esi, writeerr_string
|
||
|
out_err:
|
||
|
call sayerr
|
||
|
mov al, 5
|
||
|
call exit
|
||
|
|
||
|
; Platform-specific procedures.
|
||
|
match =WINDOWS,OS { include 'windows_sys.inc' }
|
||
|
match =LINUX,OS { include 'linux_sys.inc' }
|
||
|
|
||
|
; Data
|
||
|
macro string a, [b] {
|
||
|
common
|
||
|
db a ## _end - a
|
||
|
a db b
|
||
|
a ## _end:
|
||
|
}
|
||
|
|
||
|
string information_string, 'Usage: fasmdep [-e] [<input.fas> [<output.Po>]]',10
|
||
|
string badfile_string, 'Not .fas file',10
|
||
|
string nomemory_string, 'No memory',10
|
||
|
string in_openerr_string, 'Cannot open input file',10
|
||
|
string readerr_string, 'Read error',10
|
||
|
string out_openerr_string, 'Cannot create output file',10
|
||
|
string writeerr_string, 'Write error',10
|
||
|
|
||
|
include_variable db 'INCLUDE',0
|
||
|
TokenOn db 2,'on'
|
||
|
TokenFrom db 4,'from'
|