hi all
i wrote this littel function to get the file name from a specific path, this function has 2 operands, the first is the address of the path, the
second is the destination "buffer" which will contain the file name, for example:
"C:\somedir\SomeFile.exe"
my function supposed to put the "SomeFile.exe" in the destination buffer..
the function is written in this manner
GetFileName PROTO :DWORD, :DWORD
GetFileName proc lpPath: DWORD, dstBuffer: DWORD
=========================================================
i know that maybe there is a function in masm32 does the same mission, but i didn't search for any function, because the main
purpose of writing this function, is the training on write functions in asm...
=========================================================
there are no errors in the syntax of the code, it assembled and linked successfully, but at the run time; and once the program is
exectued, it crashes
please take a look at the code, and tell me where is the mistake(s), and correct them for me, and if you have a little time, please fill it
with your comments, so i can understand your code easily..
please also tell me if there is an easier and better way to write such funcion.
=========================================================
this is all my code, filled with my comments "these comments written as my understanding" :
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
;=================================================================
; prototype of my function
GetFileName PROTO :DWORD, :DWORD
;=================================================================
.data
ThePath db "C:\ForTest\AnyFile.exe",0
;=================================================================
.data?
TheFile db 50 dup(?)
;=================================================================
.code
comment *
my idea is:
1- Get the length of the full path
2- from the end of the path, Go back until we find "\"
3- Add 1 to that position, so we get the first char after "\"
4- copy all the chars from that position to the end of the path, to the destination "Buffer"
*
; Begin in the real work :-)
GetFileName proc lpPath:DWORD, dstBuffer:DWORD
mov eax, lpPath ; get the address of lpPath
xor edx, edx ; edx = 0
@@:
mov cl, BYTE PTR [eax]
add eax, 1
inc edx
cmp cl, 0 ;used this, because i think there is a "zero" after the string
jne @B
; all code under previous @@, is for getting the length of the path, and put it in "edx"
mov eax, lpPath
add edx, eax
cmp BYTE PTR [edx], "\"
jne @F
@@:
dec edx
cmp BYTE PTR [edx], "\"
jne @B
; all code after the previous comment, if for getting the position of "\" , and put it in "edx"
inc edx ; get the position of first char of the Real File Name :-)
mov eax, lpPath
add eax, edx ; eax now has the address of the first char after "\" char
xor edx, edx ; edx = 0, for using it in counting
push esi ; wrote this because, as i understood, i have to preserve this reg as well as (ebx, edi), correct me
; if i'm wrong here, or if there are other registers must preserved ...
mov esi, dstBuffer ; put the address of dstBuffer in "esi"
@@:
mov cl, BYTE PTR [eax + edx]
mov BYTE PTR [esi + edx], cl
inc edx
cmp cl, 0 ; used this, because i think there is a "zero" after the string
jne @B
pop esi ; get back the value which was in esi
mov eax, edx ; the return value is the length of the FileName :)
ret
GetFileName endp
; here is the test of GetFileName
start:
invoke GetFileName, Addr ThePath, Addr TheFile
invoke MessageBox, 0, Addr TheFile, Addr TheFile, MB_OK
invoke ExitProcess,0
end start
; last comment: the program is crashed immediatily after running it !!!
thank you alot in advance :-)
[edit] Realised this might sound a bit harsh/terse - it's not meant to be - I'm just in a rush [/edit]
Your main problem is here:
; all code after the previous comment, if for getting the position of "\" , and put it in "edx"
edx actually holds the address of the "\", not the offset into the string. This means that on these lines you problem continues:
add eax, edx ; eax now has the address of the first char after "\" char
as eax now points to rubbish. So when you do:
mov cl, BYTE PTR [eax + edx]
You will be accessing invalid memory locations and your program will crash.
There are many thing you can do to fix this, but the easiest idea is to replace:
mov eax, lpPath
add eax, edx ; eax now has the address of the first char after "\" char
with:
mov eax, edx
The other mistake is:
mov eax, lpPath
add edx, eax
cmp BYTE PTR [edx], "\"
jne @F
@@:
dec edx
The cmp/jne does nothing useful.
Ossa
[edit] If you read through your code, you will see that there is a way to do this using only pointers without using offsets into strings. [/edit]
From the MASM32 library:
NameFromPath proc lpPath:DWORD,lpBuffer:DWORD
Description
NameFromPath reads the filename from a complete path and returns it in the buffer specified in the parameter list.
Parameters
1. lpPath is the address of the full path that has the file name.
2. lpBuffer is the address of the buffer to receive the filename.
Return Value
The file name is returned in the buffer supplied in the second parameter.
Comments
The buffer to receive the filename must be large enough to acept the filename. For safety reasons if dealing with both long path and file name, the buffer can be made the same length as the source buffer.
Also use "Search", we had a good discussion on this, with many techniques discussed.
Regards, P1 :8)
Hi Dasar,
the comments I have posted, though might seem terse, they were just quick "jot-downs" as I was reading through your code side-by-side. Each with its own heading :bg :bg
praises:
well worked out algo...thats the way to think :U
comment:
when target is byte no need to write "byte ptr"
suggestions/bugs(?):
save restore regs at top and end
use MAX_PATH for buf lengths
double line spacing not reqiured (for post text) :bdg
code indentation ??? :red :eek
debatable (lets just "my way of thinking" :lol ):
be familiar with the so called "high level constructs" :lol They are your friends :green
.while/.endw, .if/.endif, etc :8)
GetFileNameX proc pszFullFileName:DWORD, pszOutFileName:DWORD
push esi
push edi
mov esi,pszFullFileName
.while byte ptr [esi] != 0 ;go till end of fullfilename
inc esi
.endw
.while byte ptr [esi] != '\' ;move back till last '\'
dec esi
.endw
inc esi ; point to the next char after the '\'
xor eax,eax ;counter
mov edi, pszOutFileName
.while byte ptr [esi] != 0
movsb
inc eax
.endw
mov byte ptr [edi], 0
pop edi
pop esi
ret
GetFileNameX endp
thank you
Ossa for your help, i'll try what you said
@
P1 thank you for the code
but i said:
Quotei know that maybe there is a function in masm32 does the same mission, but i didn't search for any function, because the main
purpose of writing this function, is the training on write functions in asm...
:)
and i'll search for the topic you mention above :)
shantanu_gadgil thank you for your useful comments and your code :)
Quotebe familiar with the so called "high level constructs" They are your friends
.while/.endw, .if/.endif, etc
yes, i know these instructions, but i want to be familiar with "push, pop, mov, add, xor, call, cmp, jmp, jn, jne, etc..." :)
any other hints or comments about my code from anyone is very appreciated ^_^
Dasar,
Quoteplease also tell me if there is an easier and better way to write such funcion.
Well, yes, maybe. There is a book called
Programming Windows, 5th edition, by Charles Petzold. In the 11th chapter he submits a C program called POPPAD3, which uses the Common Dialog Boxes. This program opens files using filters like *.*,*.txt,*.bak, etc. He does it by using the Common Dialog API like GetOpenFileName, of which you can read the documentation. The program opens files, searches for text, reads text replaces text. In other words, it is a poor man's file editor written to showcase the Common Dialog Boxes. This might be too heavy for what you want, but it does give an alternative high level way of implementing what you are doing. I have translated that program from C to MASM if you are interested. If not, I just thought I would mention the Common Dialog Boxes for you to peruse. Ratch
Funny...
I wrote exactly the same function for a program of mine. it even has the same name :bg
GetFileName PROC FileNameLocal:DWORD,FileNameLocalSize:DWORD
invoke GetModuleFileName,NULL,FileNameLocal,FileNameLocalSize
invoke lstrlen,FileNameLocal
mov ecx,eax
mov eax,FileNameLocal
add eax,ecx
@@:
cmp byte ptr [eax],5Ch ;first '\' from the back
jz @F
dec eax
cmp eax,FileNameLocal
jnz @B
xor eax,eax
ret
@@:
inc eax
invoke lstrcpy,FileNameLocal,eax
xor eax,eax
ret
GetFileName ENDP
;***********************************************************
Since i needed the name of only the program currently running, i put in GetModuleFileName to supply the data, but you could do it without that function as well.
here's a function changed to take as argument the complete path and which will use the same buffer to put the result in:
GetFileName PROC FileNameLocal:DWORD
invoke lstrlen,FileNameLocal
test eax,eax
jnz @F
xor eax,eax
dec eax
ret
@@:
add eax,FileNameLocal
@@:
cmp byte ptr [eax],5Ch ;first '\' from the back
jz @F
dec eax
cmp eax,FileNameLocal
jnz @B
xor eax,eax
ret
@@:
inc eax
invoke lstrcpy,FileNameLocal,eax
xor eax,eax
ret
GetFileName ENDP
The latter one should perfectly fit your needs, total size = 55 bytes
White Scorpion,
There are a couple of things worth mentioning...
@@:
cmp byte ptr [eax],5Ch ;first '\' from the back
jz @F
cmp eax,FileNameLocal
jnz @B
xor eax,eax
ret
@@:
The above code will test the same byte forever. You are missing the code to decrement eax. This is probably just an oversight but I thought I better mention it because there are those who just copy and paste code that we post.
In the both postings, however, when you abort because you have reached the beginning of the string you are setting the value of eax to zero when you should set it to one to report the error.
hth,
Casper
huh where is the dec eax missing ? :bg
Updated above code, must have accidentally removed it when updating the code ;)
2nd thing: the reason i end with 0 is that there actually is no real error:
- the stringsize is bigger then 0
- there is no \ in the string
- this means that the inputted string is already only the filename.
and since it returns the filename in the original buffer, the resulting filename can still be used as only a filename.
i can change it without any problems, and so can anyone else, but i decided it would be best to do it like this.
Dasar, have you tried stepping through your code in a debugger?
Load it using OllyDbg and keep pressing F8. This will let you see what happens as each line of code is processed. With some practice it will become trivial to fix bugs like what you are experiencing. :U
Dasar
Ossa nailed it, your program has one show stopping bug that kept your proc from working.
edx has an address not an index into the string.
Fortunately with 1 more instruction the procedure, thus program, works as intended.
inc edx ; get the position of first char of the Real File Name :-)
mov eax, lpPath
; this is the critical missing instruction, with this your program works !
sub edx, eax ; because edx is an address, to turn it into an index, subtract the address of the start of the string
dsouza123
Dasar
Since you are already are scanning the entire string for the terminating 0,
the test for a \ can be integrated at no cost, and omit later rescanning the string for the \.
A convention: esi has the address of a source string, edi has the address of a destination string.
dsouza123
GetFileName proc lpPath:DWORD, dstBuffer:DWORD
push esi
push edi
mov esi, lpPath ; get the address of lpPath
mov eax, esi ; eax has address of lpPath, and will have a valid address from the string just in case
@@:
mov cl, BYTE PTR [esi]
add esi, 1
cmp cl, "\"
jne pastbackslashtest
mov eax, esi ; eax now has the address of the first char after "\" char
pastbackslashtest:
cmp cl, 0 ; used this, because i think there is a "zero" after the string
jne @B
xor edx, edx ; edx = 0, for using it in counting
mov edi, dstBuffer ; put the address of dstBuffer in "edi"
@@:
mov cl, BYTE PTR [eax + edx]
mov BYTE PTR [edi + edx], cl
inc edx
cmp cl, 0 ; used this, because i think there is a "zero" after the string
jne @B
pop edi
pop esi ; get back the value which was in esi
mov eax, edx ; the return value is the length of the FileName :)
ret
GetFileName endp
@
Ratch QuoteI have translated that program from C to MASM if you are interested
yes, if you can, please give me the code, i might need it oneday soon :)
@
White Scorpion QuoteFunny...
I wrote exactly the same function for a program of mine. it even has the same name
lol, yes that was funny :D , and thank you alot for your code :)
@
CasperQuoteThis is probably just an oversight but I thought I better mention it because there are those who just copy and paste code that we post.
thank you for the note, btw i'm one of those whom
do not use copy/paste :D
@
Mark Jones no, i didn't use a debugger, because i nearly don't know anything about debugging, but it seems that i will have an experience with them soon :)
thank you for your reply
@
dsouza123thank you alot for your help and comments :-)
Another thing you might consider is just saving the name the first time through the string-
GetFileName proc uses esi edi lpPath:DWORD, dstBuffer:DWORD
mov esi,lpPath ; where to get the input
reset: mov edi,dstBuffer ; where to store the output
@@: lodsb ; get a character
cmp al,'\' ; is it start of possible file name?
je reset ; yes, discard this character and reset output address
stosb ; no, save possible file name character
or al,al ; are we done?
jne @b ; no, get another character
mov eax,edi ; compute length of output
sub eax,dstBuffer
dec eax
ret
thank you Jimg :)
Dasar,
OK, here it is all ZIPped up. I know it is not quite what you asked for, but it does show show how to use the Common Dialog Boxes to interrogate the user for a file name. There are two executables; one from a MASM build and the other from a C build by Charles Petzold. You can tell from the size which one is MASM or C. It's a big program for a beginner, so ask if you have any questions. Ratch
[attachment deleted by admin]
Hi, I am pretty new to MASM32 assembly, but I have used GAS (Gnu assembler) for a while. But anyway I want to ask, whould it not be possible to "declear" (not sure how it is spelled) in the .data section something like this
.data
mypath db "c:/myfolder/somefile.asd",0
dummybyte db "A"
and then work your way backwards from the address of dummybyte and look for the first "/" (last?) to show up?
or does MASM do some alignement for you so e.g if dummybyte's address is 43678 would byte at address 43677 be the 0 at the end of mypath string?
Sorry for my not so good English, I am from Iceland so I am not a native English speaker. But I try to speak correctly
thx in advance
Hi Hjortur,
Welcome on board. Let me see if I understand you correctly, you want to be able to overwrite the path "mypath" with some other data ?
If its just getting the file name without the attached path, that easy to do. Start with the address of "mypath", scan forward and store the offset of each "/" character in the same register. When you get to the terminating zero then either copy the data from the offset of the last "/" to another buffer or just return the value of the offset added to the address. Either method will work fine.
To hutch,
What he wants to ask (I think) is "Wouldn't be possible to just go _back_ from "dummybyte", rather than going "forward and back" as shown in the code?"
To Hjortur,
Yes, it is possible...but ONLY in this case. :)
The problems with that algorithm:
What if the data is changing in the variable "mypath"?
What if some shorter name, than the original, were to overwrite in place of the declared "mypath", the efffective value of "mypath" would be "C:/ZZZ/\0lder/somefile.asd",0 (The \0 is ASCII ZERO ofcourse :) :) )
See...like this many other problems are there.
So it would be appropriate to declare "mypath" as some big buffer (usually MAX_PATH) and manipulate from front.
There is very less "sanity checking" in the code I gave, as to, what if there is NO '\' in the text :eek :eek
HTH,
Shantanu
.data
mypath db "c:\fairlylongdirectoryname\secondleveldir\filename.ext",0
dummybyte db "A"
.code
mypath gets overwritten to
"c:\small\newname.ext",0,yname\secondleveldir\filename.ext",0
now searching backwards from the "A" returns the wrong filename.
Searching from the end would be quicker but if the string gets modified, it may give the wrong result.
This is exactly the reason why it is always good programming practice to zero a buffer before it is written to. By the way, if the buffer is cleared first, then a reverse search would first search for the first occurrence of a nonzero value then parse to the first occurrence of '\' while watching for the start of the buffer which means there is no '\'
A good exercise in programming basics, all around.
Paul
Thank you Ratch
Dasar,
Ok, I think I have something you are looking for and can use. It is a subroutine that enables you to find the filename in a directory string without knowing the length of the string, performing backward searches, complex parsing, zeroing buffers, or otherwise getting tied up in a knot. The subroutine is something I wrote a while back called MFNDCHAR (Multiple Find Characters). Using an advanced algo, it will search a string for as many characters as you want to push onto the stack. It will then spit out the address in EAX upon finding the first instance of any of the search characters. It searches a string in DWORD gulps, instead of teensy BYTE comparisons. The parameters are "sticky", meaning that the subroutine does not remove them from the stack. This means they can be used for another search without PUSHing the same parameters all over again. It also means that the user is responsible for balancing the stack after the last subroutine call. It is also important that a zero be PUSHed onto the stack before any other parameter. The example given shows the algo for filename searches. Simply find each backward slash and put the next character address into a variable called SaveAdr. When a zero at the end of the string is encountered, the last value of SaveAdr is the filename. It is all in the zipped file. Ratch
[attachment deleted by admin]
thx shantanu_gadgil you guessed what I was trying to say. And thank you for clearing this for me.