Is there any way to know the name of a method I'm currently in?
So that:
procedure TMyObject.SomeMethod();
begin
Writeln('my name is: ' + <hocus pocus>);
end;
would produce this output:
my name is: SomeMethod
JCL is free and has functions for that. It does depend on how well a stack trace can be made and how much debug information is present.
JclDebug.pas
function FileByLevel(const Level: Integer = 0): string;
function ModuleByLevel(const Level: Integer = 0): string;
function ProcByLevel(const Level: Integer = 0): string;
function LineByLevel(const Level: Integer = 0): Integer;
See also our TSynMapFile class.
It is able to load a .map file, and compress it into an optimized binary format. It will be much smaller than the .map itself (e.g. 900 KB .map -> 70 KB .mab). This .mab can be easily embedded within the exe. It is therefore smaller than the format used by JCL or MadExcept, and also smaller than the information embedded at compile time by Delphi.
You'll use it as such:
Map := TSynMapFile.Create; // or specify an exe name
try
i := Map.FindSymbol(SymbolAddr);
if i>=0 then
writeln(Map.Symbols[i].Name);
// or for your point:
writeln(Map.FindLocation(Addr)); // e.g. 'SynSelfTests.TestPeopleProc (784)'
finally
Map.Free;
end;
For instance, here is how it is used from our logging classes.
procedure TSynLog.Log(Level: TSynLogInfo);
var aCaller: PtrUInt;
begin
if (self<>nil) and (Level in fFamily.fLevel) then begin
LogHeaderLock(Level);
asm
mov eax,[ebp+4] // retrieve caller EIP from push ebp; mov ebp,esp
sub eax,5 // ignore call TSynLog.Enter op codes
mov aCaller,eax
end;
TSynMapFile.Log(fWriter,aCaller); // here it will call TSynMapFile for the current exe
LogTrailerUnLock(Level);
end;
end;
This method is able to retrieve the caller's address, and log its unit name, method name and line number.
Note/edit: the source code of the mORMot log unit is SynLog.pas. The updated documentation is reacheable at this URI.
If you have EurekaLog:
uses
EDebugInfo;
procedure TMyObject.SomeMethod();
begin
Writeln('my name is: ' + __FUNCTION__);
end;
There is also __FILE__, __MODULE__, __UNIT__, __LINE__, as well as a generic GetLocationInfoStr function.
However:
It will only work if you are compiling with some debug information (and the corresponding debug info provider is enabled):
EurekaLog has its own debug info format, which can be optionally compressed (which is actually not recomended, as you will spend more memory and CPU).
It also supports JCL/JEDI, Synopse/MAB, as well as .map, .tds/TD32, .dbg, .pdb.
It is NOT a constant. The name will be looked up dynamically, so it have some run-time costs.
Related
I'm getting an access violation when I close a form in my application. It seems to happen only after I have access a database a couple of times, but that doesn't seem to make sense.
I have traced through and put outputdebugstring messages in all the related OnDestroy() methods, but the AV appears to be outside of my code.
This is the text of the message:
Access violation at address 00405F7C in module
'MySoopaApplication.exe'. Read of address 00000008.
How do I find where in the application 00405F7C is?
What tools are available in Delphi 10.1 Berlin to help me with this?
Edit: added a bit more info ... when clicking "Break" the IDE always takes me to this piece of code in GETMEM.INC:
#SmallPoolWasFull:
{Insert this as the first partially free pool for the block size}
mov ecx, TSmallBlockType[ebx].NextPartiallyFreePool
Further edit: well, I found the culprit, though I can't honestly say that the debug tools got me there - they just seemed to indicate it wasn't in my code.
I had used code from the net that I used to find the Windows logged in user - this is it:
function GetThisComputerName: string;
var
CompName: PChar;
maxlen: cardinal;
begin
maxlen := MAX_COMPUTERNAME_LENGTH +1;
GetMem(CompName, maxlen);
try
GetComputerName(CompName, maxlen);
Result := CompName;
finally
FreeMem(CompName);
end;
end;
once I had replaced the code with a simple result := '12345' the AVs stopped. I have no changed it to this code:
function GetThisComputerName: string;
var
nSize: DWord;
CompName: PChar;
begin
nSize := 1024;
GetMem(CompName, nSize);
try
GetComputerName(CompName, nSize);
Result := CompName;
finally
FreeMem(CompName);
end;
end;
which seems to work and, as a bonus, doesn't cause AVs.
Thanks for your help, much appreciated.
Under Tools|Options in the IDE go to Embarcadero Debuggers | Language Exceptions and make sure Notify on Language Exceptions is checked. Also under Project Options | Compiling, make sure Debugging | Use debug DCUs is checked.
Allow the exception to happen, then go to View | Debug Windows | Call stack and you should be able to see exactly where it occurred. The fact that it occurs after db access is probably because that causes some object to be created which generates the AV when it is destroyed. Possibly because it is being Free()ed twice.
If that doesn't solve it, you may may need an exception-logging tool like madExcept mentioned by DavidH.
Read of address 00000008.
The fact that this address is a low number is suggestive of it being the address of a member of an object (because they are typically at low offsets from the base address of the object).
How do I find where in the application 00405F7C is?
With your app running, in the IDE go to Search | Go to Address. This should find it if the exception is in your application and not in some related module like a .DLL it is using. The menu item is enabled once the application is running in the IDE and stopped at a breakpoint. Also there is a compiler command line switch to find an error by address.
Others have explained how to diagnose an AV.
Regarding the code itself, there are issues with it:
Most importantly, you are not allocating enough memory for the buffer. GetMem() operates on bytes but GetComputetName() operates on characters, and in this case SizeOf (Char) is 2 bytes. So you are actually allocating half the number of bytes that you are reporting to GetComputerName(), so if it writes more than you allocate then it will corrupt heap memory. The corruption went away when you over-allocated the buffer. So take SizeOf(Char) into account when allocating:
function GetThisComputerName: string;
var
CompName: PChar;
maxlen: cardinal;
begin
maxlen := MAX_COMPUTERNAME_LENGTH +1;
GetMem(CompName, maxlen * SizeOf(Char)); // <-- here
try
GetComputerName(CompName, maxlen);
Result := CompName;
finally
FreeMem(CompName);
end;
end;
In addition to that:
you are ignoring errors from GetComputerName(), so you are not guaranteeing that CompName is even valid to pass to Result in the first place.
You should use SetString(Result, CompName, nSize) instead of Result := CompName, since GetComputerName() outputs the actual CompName length. There is no need to waste processing time having the RTL calculate the length to copy when you already know the length. And since you don't check for errors, you can't rely on CompName being null terminated anyway if GetComputerName() fails.
You should get rid of GetMem() altogether and just use a static array on the stack instead:
function GetThisComputerName: string;
var
CompName: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
nSize: DWORD;
begin
nSize := Length(CompName);
if GetComputerName(CompName, nSize) then
SetString(Result, CompName, nSize)
else
Result := '';
end;
I need to modify pchar string at runtime.
Help me with this code:
var
s:pChar;
begin
s:='123123';
s[0]:=#32; // SO HERE I HAVE EXCEPTION !!!
end.
Now i have exception in Delphi 7 !
My project is not using native pascal strings (no any windows.pas classes and others)
String literals are read only and cannot be modified. Hence the runtime error. You'll need to use a variable.
var
S: array[0..6] of Char;
....
// Populate S with your own library function
S[0] := #32;
Since you aren't using the Delphi runtime library you'll need to come up with your own functions to populate character arrays. For instance, you can write your own StrLen, StrCopy etc. You'll want to make versions that are passed destination buffer lengths to ensure that you don't overrun said buffers.
Of course, not using the built in string type will be inconvenient. You might need to come up with something a little more powerful than ad hoc character arrays.
You can:
procedure StrCopy(destination, source: PChar);
begin
// Iterate source until you find #0
// and copy all characters to destination.
// Remember to allocate proper amount of memory
// (length of source string and a null terminator)
// for destination before StrCopy() call
end;
var
str: array[0..9] of Char;
begin
StrCopy(str, '123123');
s[0]:=#32;
end.
is it possible to get source line number at runtime in Delphi?
I know JCL debug, but I want to avoid to use it. Also Assert is not exactly what I want. I would like to get something like this, where GetLineNumber would get the source line number. Is it possible to do it without MAP files (anyway will MAP file be generated when I use Assert) ? Is there any example ?
function GetLineNumber: integer;
begin
???
end;
procedure ThisWouldBeGreat;
begin
if not SomeCondition then
LogMemo.Lines.Add('Error on line: ' + IntToStr(GetLineNumber));
end;
procedure ThatsWhatIWont;
begin
Assert(not SomeCondition, 'Error');
end;
Thank you
You can indeed use Assert for this. Write a procedure that matches the signature dictated by the TAssertErrorProc type, and then do whatever you want there. To preserve the expected behavior, you should probably call the original handler after you're finished.
procedure MichaelAssertProc(const Message, Filename: string;
LineNumber: Integer; ErrorAddr: Pointer);
begin
LogMessage(...);
SysUtils.AssertErrorHandler(Message, Filename, LineNumber, ErrorAddr);
end;
Assign that procedure to System.AssertErrorProc sometime while your program starts up.
AssertErrorProc := MichaelAssertProc;
For our logging and exception tracing classes, we made a .map parser and reader.
A .map can be parsed into a binary compressed version (.mab proprietary format), which is much smaller than the original .map. For instance, a 900 KB .map file is compressed into a 70 KB .mab file - this is a much higher compression than zip.
This .mab content can be appended to the .exe, without any difference at execution, or for the end-user.
Then you can use our logging classes, or directly the .map/.mab reader class, TSynMapFile.
You have the following methods at hand:
/// retrieve a .map file content, to be used e.g. with TSynLog to provide
// additional debugging information
// - original .map content can be saved as .mab file in a more optimized format
TSynMapFile = class
public
/// get the available debugging information
// - will first search for a .map file in the .exe directory: if found,
// will be read to retrieve all necessary debugging information - a .mab
// file will be also created in the same directory (if MabCreate is TRUE)
// - if .map is not not available, will search for the .mab file in the
// .exe directory
// - if no .mab is available, will search for a .mab appended to the exe
// - if nothing is available, will log as hexadecimal pointers, without
// debugging information
// - if aExeName is not specified, will use the current process executable
constructor Create(const aExeName: TFileName=''; MabCreate: boolean=true);
/// save all debugging information in the .mab custom binary format
// - if no file name is specified, it will be saved as ExeName.mab
// - this file content can be appended to the executable via SaveToExe method
// - this function returns the created file name
function SaveToFile(const aFileName: TFileName=''): TFileName;
/// save all debugging informat in our custom binary format
procedure SaveToStream(aStream: TStream);
/// append all debugging information to an executable
// - the executable name must be specified, because it's impossible to
// write to the executable of a running process
procedure SaveToExe(const aExeName: TFileName);
/// add some debugging information according to the specified memory address
// - will create a global TSynMapFile instance for the current process, if
// necessary
// - if no debugging information is available (.map or .mab), will write
// the address as hexadecimal
class procedure Log(W: TTextWriter; Addr: PtrUInt);
/// retrieve a symbol according to an absolute code address
function FindSymbol(aAddr: cardinal): integer;
/// retrieve an unit and source line, according to an absolute code address
function FindUnit(aAddr: cardinal; out LineNumber: integer): integer;
/// return the symbol location according to the supplied absolute address
// - i.e. unit name, symbol name and line number (if any), as plain text
// - returns '' if no match found
function FindLocation(aAddr: Cardinal): RawUTF8;
/// all symbols associated to the executable
property Symbols: TSynMapSymbolDynArray read fSymbol;
/// all units, including line numbers, associated to the executable
property Units: TSynMapUnitDynArray read fUnit;
published
/// the associated file name
property FileName: TFileName read fMapFile;
/// equals true if a .map or .mab debugging information has been loaded
property HasDebugInfo: boolean read fHasDebugInfo;
end;
Thanks to this class, in just one unit, you'll have all the source code line of any location.
Open source, and working with Delphi 5 up to XE (note that the .map format changed a little bit from old to newer versions - our class try to handle it).
Note: This answer addresses the following question.
Is it possible to get source line number at runtime in Delphi?
You can't do this without a map file or something equivalent. The process of compilation leaves the source code behind.
It's not practical to litter your source code with pre-emptive checks for errors. What's more, doing so will only give you very limited information for a very limited number of faults in your code. Generally, if you can anticipate an error, you won't get it wrong. It's the errors that you don't anticipate that make it into production code.
Really you are best off using madExcept, EurekaLog or JclDebug.
var LineNumber: Integer;
procedure MyAssert(const M, F: string; L: Integer; E: Pointer);
begin
LineNumber := L;
end;
procedure TForm1.Button1Click(Sender: TObject);
var I: Integer;
S: TAssertErrorProc;
begin
I := 0;
S := AssertErrorProc;
AssertErrorProc := MyAssert;
try
Assert(I <> 0);
finally
AssertErrorProc := S;
end;
showmessage(IntToStr(LineNumber));
end;
I created my own solution for catching line numbers by assertion.
{$IFDEF TRACELOG}
try
assert(0=1,'');
except on E : Exception do
tracelog(E.Message);
end;
{$ENDIF}
where tracelog() is:
procedure TraceLog(LogMessage: WideString);
var
tfile : TextFile;
logTime : WideString;
begin
logTime := formatDateTime('YYYY-MM-DD HH:NN:SS.zzz',Now);
logMessage := Copy(LogMessage,pos(', line ',LogMessage)+7,pos(')',LogMessage) - pos(', line ',LogMessage) - 7);
Assign(tfile,SrcDir+'data\TraceLog.txt');
if FileExists(SrcDir+'data\TraceLog.txt') then Append(tfile) else Rewrite(tfile);
Writeln(tfile,'{' + logTime + '} [' + LogMessage + ']');
CloseFile(tfile);
end;
and you can Enable/Disable this with Debugger flag
{$DEFINE TRACELOG}
I hope that helps you. I found it a great solution for debuging and tracing linenumbers, let me know if it is helpful.
is it possible to get source line number at runtime in Delphi?
I know JCL debug, but I want to avoid to use it. Also Assert is not exactly what I want. I would like to get something like this, where GetLineNumber would get the source line number. Is it possible to do it without MAP files (anyway will MAP file be generated when I use Assert) ? Is there any example ?
function GetLineNumber: integer;
begin
???
end;
procedure ThisWouldBeGreat;
begin
if not SomeCondition then
LogMemo.Lines.Add('Error on line: ' + IntToStr(GetLineNumber));
end;
procedure ThatsWhatIWont;
begin
Assert(not SomeCondition, 'Error');
end;
Thank you
You can indeed use Assert for this. Write a procedure that matches the signature dictated by the TAssertErrorProc type, and then do whatever you want there. To preserve the expected behavior, you should probably call the original handler after you're finished.
procedure MichaelAssertProc(const Message, Filename: string;
LineNumber: Integer; ErrorAddr: Pointer);
begin
LogMessage(...);
SysUtils.AssertErrorHandler(Message, Filename, LineNumber, ErrorAddr);
end;
Assign that procedure to System.AssertErrorProc sometime while your program starts up.
AssertErrorProc := MichaelAssertProc;
For our logging and exception tracing classes, we made a .map parser and reader.
A .map can be parsed into a binary compressed version (.mab proprietary format), which is much smaller than the original .map. For instance, a 900 KB .map file is compressed into a 70 KB .mab file - this is a much higher compression than zip.
This .mab content can be appended to the .exe, without any difference at execution, or for the end-user.
Then you can use our logging classes, or directly the .map/.mab reader class, TSynMapFile.
You have the following methods at hand:
/// retrieve a .map file content, to be used e.g. with TSynLog to provide
// additional debugging information
// - original .map content can be saved as .mab file in a more optimized format
TSynMapFile = class
public
/// get the available debugging information
// - will first search for a .map file in the .exe directory: if found,
// will be read to retrieve all necessary debugging information - a .mab
// file will be also created in the same directory (if MabCreate is TRUE)
// - if .map is not not available, will search for the .mab file in the
// .exe directory
// - if no .mab is available, will search for a .mab appended to the exe
// - if nothing is available, will log as hexadecimal pointers, without
// debugging information
// - if aExeName is not specified, will use the current process executable
constructor Create(const aExeName: TFileName=''; MabCreate: boolean=true);
/// save all debugging information in the .mab custom binary format
// - if no file name is specified, it will be saved as ExeName.mab
// - this file content can be appended to the executable via SaveToExe method
// - this function returns the created file name
function SaveToFile(const aFileName: TFileName=''): TFileName;
/// save all debugging informat in our custom binary format
procedure SaveToStream(aStream: TStream);
/// append all debugging information to an executable
// - the executable name must be specified, because it's impossible to
// write to the executable of a running process
procedure SaveToExe(const aExeName: TFileName);
/// add some debugging information according to the specified memory address
// - will create a global TSynMapFile instance for the current process, if
// necessary
// - if no debugging information is available (.map or .mab), will write
// the address as hexadecimal
class procedure Log(W: TTextWriter; Addr: PtrUInt);
/// retrieve a symbol according to an absolute code address
function FindSymbol(aAddr: cardinal): integer;
/// retrieve an unit and source line, according to an absolute code address
function FindUnit(aAddr: cardinal; out LineNumber: integer): integer;
/// return the symbol location according to the supplied absolute address
// - i.e. unit name, symbol name and line number (if any), as plain text
// - returns '' if no match found
function FindLocation(aAddr: Cardinal): RawUTF8;
/// all symbols associated to the executable
property Symbols: TSynMapSymbolDynArray read fSymbol;
/// all units, including line numbers, associated to the executable
property Units: TSynMapUnitDynArray read fUnit;
published
/// the associated file name
property FileName: TFileName read fMapFile;
/// equals true if a .map or .mab debugging information has been loaded
property HasDebugInfo: boolean read fHasDebugInfo;
end;
Thanks to this class, in just one unit, you'll have all the source code line of any location.
Open source, and working with Delphi 5 up to XE (note that the .map format changed a little bit from old to newer versions - our class try to handle it).
Note: This answer addresses the following question.
Is it possible to get source line number at runtime in Delphi?
You can't do this without a map file or something equivalent. The process of compilation leaves the source code behind.
It's not practical to litter your source code with pre-emptive checks for errors. What's more, doing so will only give you very limited information for a very limited number of faults in your code. Generally, if you can anticipate an error, you won't get it wrong. It's the errors that you don't anticipate that make it into production code.
Really you are best off using madExcept, EurekaLog or JclDebug.
var LineNumber: Integer;
procedure MyAssert(const M, F: string; L: Integer; E: Pointer);
begin
LineNumber := L;
end;
procedure TForm1.Button1Click(Sender: TObject);
var I: Integer;
S: TAssertErrorProc;
begin
I := 0;
S := AssertErrorProc;
AssertErrorProc := MyAssert;
try
Assert(I <> 0);
finally
AssertErrorProc := S;
end;
showmessage(IntToStr(LineNumber));
end;
I created my own solution for catching line numbers by assertion.
{$IFDEF TRACELOG}
try
assert(0=1,'');
except on E : Exception do
tracelog(E.Message);
end;
{$ENDIF}
where tracelog() is:
procedure TraceLog(LogMessage: WideString);
var
tfile : TextFile;
logTime : WideString;
begin
logTime := formatDateTime('YYYY-MM-DD HH:NN:SS.zzz',Now);
logMessage := Copy(LogMessage,pos(', line ',LogMessage)+7,pos(')',LogMessage) - pos(', line ',LogMessage) - 7);
Assign(tfile,SrcDir+'data\TraceLog.txt');
if FileExists(SrcDir+'data\TraceLog.txt') then Append(tfile) else Rewrite(tfile);
Writeln(tfile,'{' + logTime + '} [' + LogMessage + ']');
CloseFile(tfile);
end;
and you can Enable/Disable this with Debugger flag
{$DEFINE TRACELOG}
I hope that helps you. I found it a great solution for debuging and tracing linenumbers, let me know if it is helpful.
I have a pointer to functions like this.
TTestEvent = function(): Boolean;
procedure ExecuteTest(aTest: TTestEvent; aType: String);
begin
if aTest then
NotifyLog(aType + ' success')
else
TestError(aType + ' failed');
end;
// Call the test
procedure TestAll;
begin
ExecuteTest(LoadParcels, 'LoadParcel');
end;
But it would be even nicer to extract the name of the function from the functionpointer aTest.
So instead of
aType + ' success'
I want something like
ExtractName(aTest) + ' success'
Can this be done in Delphi 2007 ?
If you use some of our Open Source classes, you'll be able to find the name of any symbol.
You'll have to create a .map file when building your executable, by setting "Detailed map" in your projects option.
You can then deliver the .map with the .exe, or compress the .map into our proprietary .mab format, which can be appended to the .exe. The .mab format is just a lot more efficient than .zip or other for this task: it is about 10 times smaller than the original .map file (that is, much smaller than JCLDebug or MaxExpect offers, and very much smaller than using the standard "Remote debugging symbol" embedding project option).
Then you can use the TSynMapFile class to retrieve debugging information from the .map file, or the information embedded into the .exe:
function ExtractName(aSymbolAddress: pointer): string;
var i: integer;
begin
with TSynMapFile.Create do // no name supplied -> will read from .exe
try
i := FindSymbol(aSymbolAddress);
if i>=0 then
result := Symbols[i].Name else
result := '';
finally
Free;
end;
end;
It will work for function names, but also any other symbols, like methods or global variables.
See this blog article about the class. And note that even if it is used by our mORMot framework, or its logging features, you just do not need to use the whole framework (just SynCommons.pas and SynLZ.pas units). See the Map2Mab.dpr program in "SQLite3\Samples\11 - Exception logging" sub folder to embed .map file content into an .exe.
You cannot do this with built-in features. In order to get a function name from an address you need to know the map of the executable. This is not part of an executable unless you take steps to add it.
Debugging tools like JclDebug and madExcept offer the functionality you are looking for.
You may implement a registration mechanism based on a Dictionary with
the function pointer as a Key and
the function name as a Value.
ExtractName would be a method of the dictionary.
Don't forget to make it thread safe if need be.