Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2014-05

Stack dump with possible segfault

6 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Johann Glaser

Hi!

I'm working on a project with an external library (libtcl, it is written in C) which creates some problems with back traces for exceptions.

If a custom TCL command raises an exception, I catch it and use the function DumpExceptionBackTrace to print the back trace. This function uses RaiseList which returns the ExceptObjectStack pointer. The stack frame information is filled by fpc_PushExceptObj in except.inc using the functions get_frame, get_caller_addr and get_caller_frame.

These three functions are also used by dump_stack which can be used at any point in the code (outside of exceptions).

These three functions assume, that the stack frame is stored in the RBP register (x86_64 architecture) and that every function call starts with these two assembler instructions
push %rbp
mov %rsp,%rbp
which leads to the stack layout of the return address (pushed by the "call" instruction) followed by the RBP value.

As this is true for FreePascal code (I guess), this doesn't necessarily hold for external libraries as the TCL library mentioned above.

In my particular case, GDB shows the following back trace after hitting a breakpoint in my custom TCL function implemented in INSERTTRFSMWRAPPER:

#1 0x000000000040eb3c in INSERTTRFSMWRAPPER (this=0x7ffff7fe8340, OBJC=1, OBJV=0x79d080) at trfsmgen.lpr:2552 #2 0x000000000048f86f in CMDCALLER (CLIENTDATA=0x7ffff7fe8cc0, INTERP=0x78f700, OBJC=1, OBJV=0x79d080) at ../pas-tcl/src/tcloop.pas:317 #3 0x00007ffff7aea8f8 in TclEvalObjvInternal (, , , command=0x7ffff7fe9890 'insert_trfsm_wrapper', length=20, ) at /tmp/buildd/tcl8.5-8.5.15/unix/../generic/tclBasic.c:3708 #4 0x00007ffff7aeb85b in TclEvalEx (interp=0x78f700, script=0x7ffff7fe9890 'insert_trfsm_wrapper', numBytes=<optimized out>, flags=<optimized out>, , , outerScript=0x7ffff7fe9890 'insert_trfsm_wrapper') at /tmp/buildd/tcl8.5-8.5.15/unix/../generic/tclBasic.c:4407 #5 0x00007ffff7aeb096 in Tcl_EvalEx (interp=<optimized out>, script=<optimized out>, numBytes=<optimized out>, flags=<optimized out>) at /tmp/buildd/tcl8.5-8.5.15/unix/../generic/tclBasic.c:4064 #6 0x000000000048fe65 in EVAL (this=0x7ffff7fe8380, SCRIPT=0x7ffff7fe9890 'insert_trfsm_wrapper') at ../pas-tcl/src/tcloop.pas:385 #7 0x00000000004db8a2 in EXECUTE (this=0x7ffff7fde1c0, COMMAND=0x7ffff7fe9890 'insert_trfsm_wrapper') at ../pas-tcl/src/tclcmdline.pas:230 #8 0x00000000004da790 in EXECUTE (this=0x7ffff7fde1c0, COMMAND=0x7ffff7fe9890 'insert_trfsm_wrapper') at ../pas-tcl/src/tclcmdlinepredef.pas:184 #9 0x00000000004dbe49 in COMMANDLOOP (this=0x7ffff7fde1c0) at ../pas-readline/src/cmdline.pas:81 #10 0x00000000004daf89 in COMMANDLOOP (this=0x7ffff7fde1c0) at ../pas-tcl/src/tclcmdline.pas:76 #11 0x00000000004923ad in RUN (this=0x7ffff7fe8340) at ../pas-tcl/src/tclapp.pas:63 #12 0x00000000004133ed in main () at trfsmgen.lpr:3319

This list is very reasonable.

TclEvalObjvInternal() is disassembled as follows:

Dump of assembler code for function TclEvalObjvInternal: 0x00007ffff7aea6c0 <+0>: push %r15
0x00007ffff7aea6c2 <+2>: mov %rcx,%r15
0x00007ffff7aea6c5 <+5>: push %r14
0x00007ffff7aea6c7 <+7>: push %r13
0x00007ffff7aea6c9 <+9>: push %r12
0x00007ffff7aea6cb <+11>: mov %esi,%r12d
0x00007ffff7aea6ce <+14>: push %rbp
0x00007ffff7aea6cf <+15>: mov %rdi,%rbp
0x00007ffff7aea6d2 <+18>: push %rbx
0x00007ffff7aea6d3 <+19>: mov %r9d,%ebx
...

RBP is the 5th register pushed to the stack instead of the first one. The function TclEvalEx() looks similar but pushes different registers before RBP.

Now there are two problems:
1) DumpExceptionBackTrace and dump_stack don't show the full stack trace.
2) Both functions dereference values which were taken from the stack and go through a linked list (which is not guaranteed to be working), therefore dereferencing values which might not even be pointers.

Problem 1) is inconvenient but just a cosmetic thing. I had a look at the GDB sources (backtrace_command() in gdb/stack.c and get_current_frame() and get_prev_frame_1() in gdb/frame.c), but especially the latter function is extremely complicated and trying multiple approaches to unwind the stack. I gave up on this. :-(

The more important thing is problem 2) since it potentially creates a segfault. Two counter-measures are already met in dump_stack: the previous frame must be at a lower memory address as the current frame (because "push" decreases RSP), and zero-pointers are used as stop conditions.

I suggest that additionally the RBP values are checked that they are within the program stack address range and divisible by SizeOf(PtrUInt). Further, the caller address (=return address) should be checked that it is within the program code address range. This might get a bit tricky for shared libraries.

For this debugging stuff I've worked on a small routine which parses (and caches) /proc/self/maps to find the above mentioned memory regions. If this function is of interest here, I'll happily provide it (in any license you wish).

Additionally I found UnixGetModuleByAddr in rtl/unix/dl.pp which is used via UnixGetModuleByAddrHook by GetModuleByAddr in rtl/inc/exeinfo.pp. For Windows there are similar functions.

To summarize: Stack dumps currently are a bit dangerous if using different stack frame conventions as assumed.

Bye
Hansi

Reply by email

Tomas Hajny
to Johann Glaser

On Mon, May 12, 2014 23:33, Johann Glaser wrote:

Hi,

I'm working on a project with an external library (libtcl, it is written in C) which creates some problems with back traces for exceptions.

.
.

While not completely related to your original topic, I'd like to ask you if you decided knowingly not to use the package "tcl" provided with FPC (and possibly improve / extend it for your target platform) and if so, what were your reasons.

I ask because I'm not sure to which extent it's used and/or maintained, especially considering that it supports a fairly limited set of platforms measured by current FPC span and that the included name of the TCL DLL for Win32 does not seem to match currently available TCL version.

Should the package not to be useful any longer, we should probably either update it (if anyone volunteers to maintain it, because I suspect that the original maintainer is not involved actively any longer), or consider deprecating it.

Thanks

Tomas

Reply by email

Johann Glaser
to Tomas Hajny

Hi!

Show quoted text 21 lines
Hi, > I'm working on a project with an external library (libtcl, it is written > in C) which creates some problems with back traces for exceptions. . . While not completely related to your original topic, I'd like to ask you if you decided knowingly not to use the package "tcl" provided with FPC (and possibly improve / extend it for your target platform) and if so, what were your reasons. I ask because I'm not sure to which extent it's used and/or maintained, especially considering that it supports a fairly limited set of platforms measured by current FPC span and that the included name of the TCL DLL for Win32 does not seem to match currently available TCL version. Should the package not to be useful any longer, we should probably either update it (if anyone volunteers to maintain it, because I suspect that the original maintainer is not involved actively any longer), or consider deprecating it.

I have to admit that I didn't consider that there could already be a TCL unit available. Therefore I did a full header translation for TCL 8.4 (which was the current version at that time). Additionally I added an object oriented wrapper to ease the programming.

Please see at https://github.com/hansiglaser/pas-tcl for the sources.

I use this library (together with pas-readline) in several projects (e.g. "eztool" on Github), some of which are actively in development but not yet published. It currently works perfectly with TCL 8.5, but I didn't (yet) update the header translations.

I'd be happy for any help improving the library or even inclusion in FPC!

Bye
Hansi

PS: I've mentioned the TCL library in the fpc-pascal list some time ago: http://lists.freepascal.org/fpc-pascal/2012-September/035177.html

Reply by email

Tomas Hajny
to Johann Glaser
Johann Glaser wrote 43 lines
Hi! > Hi, > > > I'm working on a project with an external library (libtcl, it is > written > > in C) which creates some problems with back traces for exceptions. > . > . > > While not completely related to your original topic, I'd like to ask you > if you decided knowingly not to use the package "tcl" provided with FPC > (and possibly improve / extend it for your target platform) and if so, > what were your reasons. > > I ask because I'm not sure to which extent it's used and/or maintained, > especially considering that it supports a fairly limited set of > platforms > measured by current FPC span and that the included name of the TCL DLL > for > Win32 does not seem to match currently available TCL version. > > Should the package not to be useful any longer, we should probably > either > update it (if anyone volunteers to maintain it, because I suspect that > the > original maintainer is not involved actively any longer), or consider > deprecating it. I have to admit that I didn't consider that there could already be a TCL unit available. Therefore I did a full header translation for TCL 8.4 (which was the current version at that time). Additionally I added an object oriented wrapper to ease the programming. Please see at https://github.com/hansiglaser/pas-tcl for the sources. I use this library (together with pas-readline) in several projects (e.g. "eztool" on Github), some of which are actively in development but not yet published. It currently works perfectly with TCL 8.5, but I didn't (yet) update the header translations. I'd be happy for any help improving the library or even inclusion in FPC!

Since you already use TCL, could you please check if the version included with FPC is still useable at all with the currently available TCL version? I guess that you'd be probably able to modify one of your test programs to only use functions available in the older TCL version (or create another simple test for that purpose if that would be easier)?

Depending on the result (and compatibility of the relevant licences), we have to agree with other members of the FPC core team on the most sensible approach. I noticed that there are some obvious differences in used types (pointer versus PChar, minor differences in parameter or record member names, etc.) between the two translations, but these might not impose a major issue.

PS: I've mentioned the TCL library in the fpc-pascal list some time ago: http://lists.freepascal.org/fpc-pascal/2012-September/035177.html

I see; I probably missed it then.

BTW, I noticed that you refer to file license.terms in multiple places of your repository, but I couldn't find that file there.

Tomas

Reply by email

Johann Glaser

Hi again!

Johann Glaser wrote 24 lines
Hi! I'm working on a project with an external library (libtcl, it is written in C) which creates some problems with back traces for exceptions. If a custom TCL command raises an exception, I catch it and use the function DumpExceptionBackTrace to print the back trace. This function uses RaiseList which returns the ExceptObjectStack pointer. The stack frame information is filled by fpc_PushExceptObj in except.inc using the functions get_frame, get_caller_addr and get_caller_frame. These three functions are also used by dump_stack which can be used at any point in the code (outside of exceptions). These three functions assume, that the stack frame is stored in the RBP register (x86_64 architecture) and that every function call starts with these two assembler instructions push %rbp mov %rsp,%rbp which leads to the stack layout of the return address (pushed by the "call" instruction) followed by the RBP value. As this is true for FreePascal code (I guess), this doesn't necessarily hold for external libraries as the TCL library mentioned above.

News on that front: Today I investigated a duplicate key exception in FGL.TFPSMaps.Add(AKey: Pointer): Integer. In the current Debian package fp-units-rtl-2.6.4 2.6.4+dfsg-2 this seems to be compiled with looots of optimization and without stack frame:

0000000000001918 <FGL_TFPSMAP_$__ADD$POINTER$$LONGINT>: 1918: 48 81 ec 18 01 00 00 sub $0x118,%rsp 191f: 48 89 9c 24 08 01 00 mov %rbx,0x108(%rsp) 1926: 00
1927: 4c 89 a4 24 10 01 00 mov %r12,0x110(%rsp) 192e: 00
192f: 48 89 fb mov %rdi,%rbx 1932: 49 89 f4 mov %rsi,%r12

Even GDB couldn't trace back the stack there. :-O

Bye
Hansi

Reply by email

Sven Barth
to Johann Glaser
"Johann Glaser" wrote 18 lines
> As this is true for FreePascal code (I guess), this doesn't necessarily > hold for external libraries as the TCL library mentioned above. News on that front: Today I investigated a duplicate key exception in FGL.TFPSMaps.Add(AKey: Pointer): Integer. In the current Debian package fp-units-rtl-2.6.4 2.6.4+dfsg-2 this seems to be compiled with looots of optimization and without stack frame: 0000000000001918 <FGL_TFPSMAP_$__ADD$POINTER$$LONGINT>: 1918: 48 81 ec 18 01 00 00 sub $0x118,%rsp 191f: 48 89 9c 24 08 01 00 mov %rbx,0x108(%rsp) 1926: 00 1927: 4c 89 a4 24 10 01 00 mov %r12,0x110(%rsp) 192e: 00 192f: 48 89 fb mov %rdi,%rbx 1932: 49 89 f4 mov %rsi,%r12 Even GDB couldn't trace back the stack there. :-O

Code in release packages is normally compiled using -O2, which can result in hard to follow backtraces.

Regards,
Sven

Reply by email