Laaca
Czech republic, 27.03.2021, 20:06 |
Not widely known problem in programming? (Developers) |
I would like to share my experience with source of several my very mysterious programming bugs. In several cases I for many hours tried to debug some part of my program and was not able to find the bug. Sometimes I've given to up and sometimes I've avoided the problem with some ugly hack but never really found what was wrong.
The typical symptom is that everything works just fine but it stops working after some change in program - usually in very different part of program. The term "stops working" means that the program crashes. Sometimes crashes only the program but sometimes whole DOS.
The debugger usually reports the error into some graphic procedure but it is apparently misleading.
Recently I investigated such behaviour in my editor Blocek and found the reason. I suspected some uninitialised pointer bug (known and frequent problem) but found something what was new for me.
In several pieces of my code I found that I am referencing global pointer to local stack.
The problem is that everything works OK for very long time but "sometimes" - usually in assembler graphic functions which work with such pointer the crash occurs.
Such function touches the memory where before was our data by own purposes and because of that the referenced value is overwritten and the behaviour is after that undefined.
I tried to strip the problem to be the example as simple as possible. Look at the code bellow. The bug is in the procedure "Test_Obj.Init" where I am assigning the "message" pointer into local stack.
Than the Test_Obj.Display works but there is still the permanent danger that in case of extending the program the incrimined part of memory will be overwritten and the program will crash or will not work in the right way.
Maybe you thing that I am stupid because such obvious bug can make only a idiot but trust me - if is it a real-life code (not this trivial sample) it can be very difficult to find it.
Have you ever faced to this type of bug?
Does the compilers (I am interrested mainly inf Freepascal) have some mechanism which does -something- with the local stack to demask such latent programming bug?
And the promised code example:
Program ObjTest;
type
test_obj=object
message:pchar;
procedure Init;
procedure Display;
end;
Procedure test_obj.Init;
var s:string; {variable on local stack}
begin
s:='Hello'#0;
message:=@s[1]; {public reference to local stack}
end;
Procedure test_obj.Display;
begin
writeln(message);
end;
var to:test_object;
begin
to.Init;
to.Display;
end. --- DOS-u-akbar! |
ecm
Düsseldorf, Germany, 27.03.2021, 20:30
@ Laaca
|
Not widely known problem in programming? |
I'd call this a type of "use after free". --- l |
tkchia
28.03.2021, 08:02
@ ecm
|
Not widely known problem in programming? |
Hello ecm, hello Laaca,
> I'd call this a type of "use after free".
Yep, me too. The program is effectively using memory after it is considered "freed" back to the system.
Apparently AddressSanitizer can sometimes detect such "stack use after return" bugs. But I am not sure if AddressSanitizer works (or can be made to work) with Free Pascal, or even with 32-bit DPMI.
Thank you! --- https://gitlab.com/tkchia · https://codeberg.org/tkchia · 😴 "MOV AX,0D500H+CMOS_REG_D+NMI" |
marcov
28.03.2021, 17:44
@ tkchia
|
Not widely known problem in programming? |
> > I'd call this a type of "use after free".
>
> Yep, me too. The program is effectively using memory after it is
> considered "freed" back to the system.
>
> Apparently AddressSanitizer can
> sometimes
> detect such "stack use after return" bugs. But I am not sure if
> AddressSanitizer works (or can be made to work) with Free Pascal, or even
> with 32-bit DPMI.
No. The page it states its patches for LLVM and gcc, not an independent tool like valgrind. |
ecm
Düsseldorf, Germany, 28.03.2021, 21:52
@ Laaca
|
Not widely known problem in programming? |
> Program ObjTest;
> type
> test_obj=object
> message:pchar;
> procedure Init;
> procedure Display;
> end;
>
> Procedure test_obj.Init;
> var s:string; {variable on local stack}
> begin
> s:='Hello'#0;
> message:=@s[1]; {public reference to local stack}
> end;
>
> Procedure test_obj.Display;
> begin
> writeln(message);
> end;
>
> var to:test_object;
> begin
> to.Init;
> to.Display;
> end.
I was curious about the meaning of your example. The FreePascal description of the pchar type confirms my expectation: the #0 inserts a zero terminator into the s variable, and @s[1] makes a pointer to the message data. (I think 0 would point to the string-type object's length counter?) --- l |
Laaca
Czech republic, 29.03.2021, 05:04
@ ecm
|
Not widely known problem in programming? |
> I was curious about the meaning of your example. The FreePascal
> description of
> the pchar type confirms my expectation: the #0 inserts a zero
> terminator into the s variable, and @s[1] makes a pointer to the message
> data. (I think 0 would point to the string-type object's length counter?)
Yes, exactly. S[0] is the length of the string (so this is the reason why this type of string variable is limited to 255 chars) and the chars begin from S[1] --- DOS-u-akbar! |
bretjohn
Rio Rancho, NM, 29.03.2021, 20:03
@ tkchia
|
Not widely known problem in programming? |
> > I'd call this a type of "use after free".
>
> Yep, me too. The program is effectively using memory after it is
> considered "freed" back to the system.
They are both correct. You simply cannot reference a local variable from a different local procedure and expect it to be reliable (sometimes it will work and other times it won't). You can only reference local variables from within the same procedure that declared them -- that's why they are called local. In your example, you will either need to make "message" a global variable, or make Init and Display be the same procedure. You could also maybe do some sort of indirect referencing by passing pointers, but still need to make sure you aren't releasing memory before you try to reference it. |
Laaca
Czech republic, 29.03.2021, 22:24
@ bretjohn
|
Not widely known problem in programming? |
> You simply cannot reference a local variable from a different local procedure and expect it to be reliable (sometimes it will
> work and other times it won't).
Sure. As I said - in such trivial example it is obvious but in the real world can be difficult do find such bug because debugger does not help you with it much.
The point is whether the modern compilers (like DJGPP/GCC or Freepascal) do offer some help with this.
For example Freepascal has a compiler parameter "-gt"
What does the documentation say about this:
"-gt Trash local variables. This writes a random value to local variables at procedure start. This can be used to detect uninitialized variables."
Nice. But is does not help with my bug. However if the local variable trashing would be in the procedure ending it could maybe help. --- DOS-u-akbar! |
ecm
Düsseldorf, Germany, 29.03.2021, 22:26
@ bretjohn
|
Not widely known problem in programming? |
> They are both correct. You simply cannot reference a local variable from a
> different local procedure and expect it to be reliable (sometimes it will
> work and other times it won't). You can only reference local variables
> from within the same procedure that declared them -- that's why they are
> called local.
The exception is of course if you pass local variable references from one function to another function, where the latter is currently being called from within the former. --- l |
bretjohn
Rio Rancho, NM, 29.03.2021, 23:02
@ Laaca
|
Not widely known problem in programming? |
I don't think a compiler is going to help much in this case. You've set up "message" as a global variable, but then equate it to a variable inside a local procedure called "s" so "s" and "message" are really the same thing. When "s" disappears, so does "message". So, while "message" appears to be global it really isn't. I'm not sure any kind of debugger/troubleshooter can catch things when you have pointers and redirection like that.
That is simply bad programming practice. |
marcov
30.03.2021, 12:05
@ bretjohn
|
Not widely known problem in programming? |
> I don't think a compiler is going to help much in this case.
You can try to generate warnings with static analysis (in compiler or with a separate tool) if a pointer to the stack is returned. But this is probably not watertight, though probably would cover many common accidental cases.
Peganza used to have a static analysis tool for Pascal/Delphi. A former employer had a license for it, and it seems they have expanded since. It might be worth looking at if they implemented something like that.
AQTime might also have something |