The MASM Forum Archive 2004 to 2012

General Forums => The Workshop => Topic started by: 2-Bit Chip on August 23, 2009, 03:32:45 AM

Title: Get Function Name
Post by: 2-Bit Chip on August 23, 2009, 03:32:45 AM
Would there be a way to get the function name (as a string) that called SetLastError? Or, will I just have to live with the
"0xXXXXXXXX - Error description here"?

Sincerely,

Chip
Title: Re: Get Function Name
Post by: dedndave on August 23, 2009, 03:47:39 AM
i was looking at that myself
all the text is right there in the data segment - you just have to find a way to connect the dots
when the pe format exe gets loaded, windows looks through the list to resolve externals
problem is, it wasn't meant to be used the other way around
but, i think a routine could be written to create a "reverse" list
for smaller programs, you may be just as well off to create the list yourself
but, for larger programs that use a lot of different functions, the routine might save some space
Title: Re: Get Function Name
Post by: 2-Bit Chip on August 23, 2009, 04:04:27 AM
I really would like to display an error as "[Function Name] - [Error Code] - [Error Description]."

I guess it would be more informative.
Title: Re: Get Function Name
Post by: 2-Bit Chip on August 23, 2009, 04:21:25 AM
Well.. I am using this simple, little piece of code to show the errors:


fThrowError:
    mov edi, alloc$(MAX_PATH)
    mov edx, alloc$(MAX_PATH)
    invoke GetLastError
    invoke dw2hex, eax, edx
    invoke wsprintf, edi, CADD("0x%s - %s"), edx, LastError$()
    invoke MessageBoxA, NULL, edi, NULL, MB_OK or MB_ICONERROR
    exit
Title: Re: Get Function Name
Post by: dedndave on August 23, 2009, 04:21:33 AM
it gets a bit wordy, but you can also display the address of the invoke
it shouldn't be too difficult to create an error routine that can be used for all invokes in the program
it could call GetLastError and give the error code
pass it the address of a string that identifies the invoke, as well as the address

.
.
        INVOKE  GetPriorityClass,
                hProc
LabelA:
        or      eax,eax
        jnz     Success

        mov     edx,offset GetPrioClsStr  ;"GetPriorityClass",0
        mov     ecx,offset LabelA
        call    DisplayError
        jmp     ErrorExit

Success:
.
.
ErrorExit:
.
.
DisplayError    PROC

        push    ecx
        push    edx
        INVOKE  GetLastError
        push    eax
        print   chr$('Error: ')
        pop     eax
        print   str$(eax),' in Function: '
        pop     eax
        print   eax
        print   chr$(' at Address: ')
        pop     eax
        sub     eax,5
        print   uhex$(eax),13,10
        ret

DisplayError    ENDP
Title: Re: Get Function Name
Post by: 2-Bit Chip on August 23, 2009, 04:22:23 AM
Thank you Dan. I'm not real hip with wordiness :P
Title: Re: Get Function Name
Post by: Tedd on August 24, 2009, 09:51:10 AM
This might be useful..

;hWnd = global handle to your main app window (you could just add it as a param to this function)
;AppName = a string giving the name of your main app (for the messagebox titlebar)

ErrorMessageBox proc pMsg:DWORD,errCode:DWORD
    LOCAL buff[512]:BYTE
    push edi
    lea edi,[buff]
    invoke lstrcpy, edi,pMsg
    mov ecx,SIZEOF buff
    lea edx,[edi+eax]
    sub ecx,eax
    invoke FormatMessage, FORMAT_MESSAGE_FROM_SYSTEM,NULL,errCode,0,edx,ecx,NULL
    invoke MessageBox, hWnd,edi,ADDR AppName,MB_OK or MB_ICONERROR
    pop edi
    ret
ErrorMessageBox endp


Example usage..

errFileOpen     db "Error opening file.",13,10,0

    invoke GetLastError
    invoke ErrorMessageBox, ADDR errFileOpen,eax



As for getting the function name, it's quite a bit extra as they're not (normally) stored in the exe. You could add them as debug info and then extract them from that. Or, build a table of the offsets of functions in your code, then use that in the error function to see who the caller was (get the return address from the stack, and see where that falls in the table.)
Title: Re: Get Function Name
Post by: dedndave on August 24, 2009, 10:08:16 AM
Hi Tedd
i see them in my exe file
in this case, it is a console app using these command lines

ml /c /coff %1.asm
Link /SUBSYSTEM:CONSOLE /OPT:NOREF %1.obj

ExitProcess.¥.FillConsoleOutputAttribute..Æ.FlushConsoleInputBuffer.ï.GetConsoleCursorInfo..ò.
GetConsoleMode..õ.GetConsoleScreenBufferInfo....GetCurrentProcess...GetCurrentThread..H.
GetPriorityClass..T.GetProcessAffinityMask..j.GetStdHandle..y.GetSystemTimeAsFileTime.p.
SetConsoleCursorInfo..q.SetConsoleCursorPosition..r.SetConsoleMode..u.SetConsoleTextAttribute.v.
SetConsoleTitleA....SetPriorityClass..ž.SetProcessAffinityMask..­.SetThreadPriority.».Sleep.â.
VirtualProtect..û.WriteFile.kernel32.dll..Î._getch...._kbhit..msvcrt.dll
Title: Re: Get Function Name
Post by: Tedd on August 24, 2009, 11:51:47 AM
Ah, my mistake - I read it wrong; I took it as 'which of the functions in my code produced the error.'

Yeah, all the imported functions have their names stored in the executable so they can be imported. There's also a table produced when the program is loaded that receives the addresses of each of those functions, so they can be called. So that could potentially be used for finding which function was called last, but it would be quite hackish and depend on not doing anything at all after calling the function.

I presume the reason for checking where the error was produced is for debugging, so you can find where you went wrong in your code. A much easier way would be to use the _LINE_ directive to tell the error reporter which line it was called from, but masm doesn't appear to have one (or I can't find it) ::)
But, given a source address, you can still easily find where it was called from, and look what was called before that - it should be clear from the error message what caused it (should :bdg)
Title: Re: Get Function Name
Post by: dedndave on August 24, 2009, 01:54:18 PM
what i was thinking was writing a little routine that locates the address of the text string for a specific function call
that way, you do not have to duplicate the text in data (saves space AND some typing - lol)
as i mentioned in an earlier post, the tables are made backwards for that purpose, of course
besides the IAT that gets filled with resolved addresses, each string has an offset into the IAT (presumably)
also, at the end of each group of text strings is the library
in other words, all the kernel32.dll's are listed, then text that says "kernel32.dll"
then, as in my example, the msvcrt.dll functions are listed
you could write an error routine that had a disply like this

Error: 5 in Function: SetProcessAffinityMask from Library: kernel32.dll at Address 40201234

the text for "SetProcessAffinityMask" and "kernel32.dll" could come from the already existing list
i know this isn't that hard, and when i get done working on my current project, i may have a look at it
EDIT - the list should always contain ExitProcess and is alphabetically arranged, too
Title: Re: Get Function Name
Post by: sinsi on August 24, 2009, 02:06:03 PM
>the text for "SetProcessAffinityMask" and "kernel32.dll" could come from the already existing list
I think the only way in masm is to use GetProcAddress and import all of your functions, but that would be a pain.
Is there any way of making a .obj file with the import section to link with? Lots of externdef's for the .inc file though...
Title: Re: Get Function Name
Post by: dedndave on August 24, 2009, 02:07:45 PM
the text exists in the .data section
no reason you couldn't locate it and use it
the IAT entry is easy enough - the invoke has that address
lol Sinsi - you're gonna make me stop working on my current project and make this - lol
Title: Re: Get Function Name
Post by: jj2007 on August 24, 2009, 04:07:46 PM
Quote from: Tedd on August 24, 2009, 11:51:47 AM
A much easier way would be to use the _LINE_ directive to tell the error reporter which line it was called from, but masm doesn't appear to have one

Try this one :bg

include \masm32\include\masm32rt.inc

.code
start:

MsgBox 0, str$(@Line), "The error line", MB_OK

exit

end start
Title: Re: Get Function Name
Post by: dedndave on August 24, 2009, 09:27:05 PM
that's cool Jochen - a masm "pre-assigned equate"
it needs a little adjustment - lol
the line where you get the line number is at least one more than the error line (actually, 3 would be good)

        mov     eax,@Line
        dec     eax

i was looking in the masm manual - there are a few other useful values, also
@Date and @Time can be used to mark a file with assembly time info

EDIT - what i meant by "actually, 3 would be good" - a typical example

        INVOKE  SomeErrorProneFunction,parm2,parm1
        or      eax,eax
        jnz     Successful
        push dword ptr @Line
        INVOKE  GetLastError
        pop     edx
        sub     edx,3           ;adjust to point to the invoke line
Title: Re: Get Function Name
Post by: Tedd on August 25, 2009, 11:14:40 AM
I thought I'd seen one before :cheekygreen:
Title: Re: Get Function Name
Post by: dedndave on August 25, 2009, 02:45:15 PM
this works too...

        INVOKE  SomeErrorProneFunction,parm2,parm1
        or      eax,eax
        jnz     Successful
        push dword ptr @Line-3  ;adjust to point to the invoke line
        INVOKE  GetLastError
        pop     edx
Title: Re: Get Function Name
Post by: jj2007 on August 25, 2009, 09:04:53 PM
What would have been really cool: If M$ had decided to store the address of the function that called SetLastError...

Here_I_am:
invoke SetLastError, ERROR_INVALID_NAME


00401031       |.  6A 7B                   push 7B                           ; /Error = ERROR_INVALID_NAME
00401033       |.  E8 BA000000             call <jmp.&kernel32.SetLastError> ; \SetLastError
...
004010F2        $- FF25 68114000           jmp near [<&kernel32.SetLastError>;  ntdll.RtlSetLastWin32Error
...
RtlSetLastWin3>  8BFF                      mov edi, edi                      ; ntdll.7C920738
7C920342         55                        push ebp
7C920343         8BEC                      mov ebp, esp
7C920345         64:A1 18000000            mov eax, fs:[18]
7C92034B         8B4D 08                   mov ecx, [ebp+8]
7C92034E         8948 34                   mov [eax+34], ecx
7C920351         5D                        pop ebp
7C920352         C2 0400                   retn 4


Interesting proc, isn't it? 50% are just useless; this should do the job:
7C920345         64:A1 18000000            mov eax, fs:[18]
7C92034B         8B4D 08                   mov ecx, [esp+8]
7C92034E         8948 34                   mov [eax+34], ecx
7C920352         C2 0400                   retn 4


Or, a little bit shorter:
MyError proc
db 64h, 0A1h, 18h, 0h, 0h, 0h ; mov eax, fs:[18]
pop ecx
pop [eax+34h]
jmp ecx
MyError endp
Title: Re: Get Function Name
Post by: dedndave on August 25, 2009, 11:11:25 PM
i have developed the basis for extracting the function name, as well as the library name
we all know about the IAT jmp tables
the data table that those jumps point to gets adjusted at load-time (relocated) to indicate the function addresses
but !   :bg  there is also another copy of that table that does not get
altered at load-time that may be used to get the addresses of the strings
at the end of "our" .CONST segment definitions is a 4-aligned dword that points to the table (after adding the 400000h)

        INCLUDE \masm32\include\masm32rt.inc

        .const

OurConstDefines db 1

END_OF_CONST_SEGMENT EQU $

        .data
        .data?
        .code

start:  INVOKE  GetCurrentProcess
        mov     edx,END_OF_CONST_SEGMENT+3
        and     dl,0FCh
        mov     ecx,400000h
        mov     edx,[edx]
        add     edx,ecx
        inc     ecx
        inc     ecx

loop00: mov     eax,[edx]
        or      eax,eax
        jz      exit00

        add     eax,ecx
        push    ecx
        push    edx
        print   eax
        print   chr$(13,10)
        pop     edx
        add     edx,4
        pop     ecx
        jmp     loop00

exit00: exit

        END     start

i put GetCurrentProcess in there just to have another function in the list
GetStdHandle and WriteFile are from the print macro, of course
output:

GetCurrentProcess
GetStdHandle
WriteFile
ExitProcess

in these tables are dwords that (again, after adding the 400000h) point to the strings
each string is preceeded by a 2-aligned word value that we may ignore and terminated with a 0
there is a seperate table for each library - the tables are seperated by a dword 0
also, at the end of each string group is the name of the library that the group of functions came from
i am working on code that will also support msvcrt.dll errors....
Title: Re: Get Function Name
Post by: dedndave on August 26, 2009, 03:50:11 AM
maybe someone in here knows how to do what i want to do
i can use FormatMessage to get API error descriptions - no problem, there
i want to apply the same technique for msvcrt.dll errors
here is my current code, to give you an idea...

include \masm32\include\masm32rt.inc

.data
DirName db 'C:\xyzzy',0    ;a path that does not exist
crtName db 'msvcrt.dll',0

.data?
ErrBuf  db 1024 dup(?)

.code
start:
push offset DirName
call crt__chdir
add esp,4
call crt__errno
push dword ptr [eax]
invoke GetModuleHandle,offset crtName
pop edx
invoke FormatMessage,FORMAT_MESSAGE_FROM_HMODULE,eax,edx,0,offset ErrBuf,1024,NULL
print offset ErrBuf
exit

end start

i am getting the correct error code (2=path not found) from errno
the module handle should be right
so, it boils down to FormatMessage not doing what i was hoping
any suggestions ?

EDIT - there aren't that many crt error codes - i suppose i could make a table and map them to winAPI codes
Title: Re: Get Function Name
Post by: MichaelW on August 26, 2009, 07:01:46 AM
I tried a C app where I attempted to get the error descriptions with  _RTC_GetErrDesc (http://msdn.microsoft.com/en-us/library/a74161bs(VS.80).aspx), but I could only get a few of them because _RTC_NumErrors always reported 4, even when I linked with the debug library. In any case, there is a list  here (http://msdn.microsoft.com/en-us/library/t3ayayh1(VS.80,loband).aspx).
Title: Re: Get Function Name
Post by: dedndave on August 26, 2009, 10:32:14 AM
thanks for the tip, Michael
that function is not part of msvcrt
it is defined in rtcapi.h
i am guessing (lol) that it is in RunTmChk.lib
i get lost looking at C code - let alone their include files - lol
the rtcapi.h file i have says these functions are deprecated
any ideas how to declare the externdef ?
Title: Re: Get Function Name
Post by: Tedd on August 26, 2009, 10:44:48 AM
FormatMessage will try to look up a string for the error code from the module you give it a handle to - if there are none, obviously it can't find them. In general, just use FORMAT_MESSAGE_FROM_SYSTEM (as in the code I posted) - that covers all standard error codes, and many from modules that have already been loaded by the system.
Title: Re: Get Function Name
Post by: dedndave on August 26, 2009, 11:25:33 AM
thanks Tedd - that part works
the msvcrt error codes are different, however
the text strings are in the msvcrt.dll file - they just aren't available as resources
i may be able to use perror or one of the other crt functions to get the strings
it's just a matter of playing with it - lol
a good experience, anyways - i get to know my way around the crt a bit
Title: Re: Get Function Name
Post by: dedndave on August 26, 2009, 12:08:32 PM
got it
crt_strerror returns the address of the string

EDIT
there are two functions, crt__strerror and crt_strerror
they don't behave like the documentation suggests (big surprise)
crt_strerror seems to be the one to use
Title: Re: Get Function Name
Post by: dedndave on August 26, 2009, 01:21:10 PM
msvcrt.dll error message list

0       No error
1       Operation not permitted
2       No such file or directory
3       No such process
4       Interrupted function call
5       Input/output error
6       No such device or address
7       Arg list too long
8       Exec format error
9       Bad file descriptor
10      No child processes
11      Resource temporarily unavailable
12      Not enough space
13      Permission denied
14      Bad address
15      Unknown error
16      Resource device
17      File exists
18      Improper link
19      No such device
20      Not a directory
21      Is a directory
22      Invalid argument
23      Too many open files in system
24      Too many open files
25      Inappropriate I/O control operation
26      Unknown error
27      File too large
28      No space left on device
29      Invalid seek
30      Read-only file system
31      Too many links
32      Broken pipe
33      Domain error
34      Result too large
35      Unknown error
36      Resource deadlock avoided
37      Unknown error
38      Filename too long
39      No locks available
40      Function not implemented
41      Directory not empty
42      Illegal byte sequence


now, i just have to figure out crt__DOSerrno and WSAGetLastError - lol