4

In my Delphi XE2 32-bit application (Update 4 Hotfix 1 Version 16.0.4504.48759) , I'm using the Format() routine to log pointer values.

For example:

Format('MyObject (%p)', [Pointer(MyObject)]);

However, the resulting string sometimes contains garbage characters (e.g., in this case '?' or '|' in place of hex digits):

MyObject (4E?|2010)

I also get the same result when replacing '%p' with '%x' like so:

Format('MyObject (%x)', [Integer(MyObject)]);

However, using an integer value always works:

Format('MyObject (%d)', [Integer(MyObject)]);

MyObject (1291453120)

Is there a bug that I'm unaware of or can this be related to the problem experienced here?

Why does Format crash when anything but "%s" is used with a Variant?

UPDATE

I've accepted Jeroen's answer as it led me to the solution by process of elimination. After the situation with starting the app via F7 (as per the comment), I figured that something must be going wrong much earlier in the process. On a hunch, I disabled madExcept from its IDE menu, rebuilt the app, and the problem disappeared. Evidently, whatever code madExcept was linking into my application was causing an overwrite in the SysUtils constant TwoHexLookup. Re-enabling madExcept and rebuilding (without any other changes on my part) also worked, so there must have been some corruption during the linking phase.

The strategy Jeroen outlined for detecting memory corruption was a useful exercise and should prove valuable if I encounter a similar situation.

Community
  • 1
  • 1
Doug
  • 317
  • 2
  • 5
  • 12
  • 1
    If this is true, it is certainly a bug... Has this ever happend to you in an empty project? – Andreas Rejbrand Feb 13 '13 at 19:01
  • Looks like a bug, do you have a example to reproduce it? – jachguate Feb 13 '13 at 19:08
  • Until you can reproduce this, it's a little pointless. We cannot help. You just need a simple program that demonstrates the fault. Then you submit a QC report. – David Heffernan Feb 13 '13 at 19:24
  • I'm trying to reproduce it with a simple app now, but so far no luck. It happens in several parts of two different, well tested multi-threaded applications, but I still assumed it was a problem with my code (still do). However, the fact that it never occurs with %d perplexed me and hence why I asked the question. – Doug Feb 13 '13 at 19:30
  • 1
    @Doug: Might it be possible that you interpret the result incorrectly? For instance, is `Format` really `System.Format`? What method do you use to read the string returned by `Format`? Are you sure `MyObject` is what you think it is? Is there any memory corruption in your application? – Andreas Rejbrand Feb 13 '13 at 19:38
  • What you are describing is pretty much implausible. If there was floating point -> text involved then I'd suspect a threading/CW bug. Or if your output depended on `FormatSettings` then again threading could be a problem. But it boils down to a call to `IntToHex` and a mem copy. Hard for RTL to screw that up. Your code is the likely cause of the problem. Until you can create a repro I suggest you delete the question. At the moment this is a non-question. – David Heffernan Feb 13 '13 at 20:05
  • 1
    Thanks for the comments so far. Andreas, I have confirmed that I'm calling System.Format and the variables involved. @David, thanks for clarifying that it's unlikely a bug in the RTL, but in my code. If I can't reproduce it shortly, I'll delete this question. FWIW, I've enabled all the options in FastMM I can (CheckForHeapCorruption, FullDebugMode, etc.) but so far it hasn't turned up anything. – Doug Feb 13 '13 at 20:46
  • Can you still reproduce it occasionally if you do a `var PointerString: string; PointerString := Format('%p', ...)`, then check `PointerString` to be fully hexadecimal, if it is not `INT 3` (or breakpoint), if it is hexadecimal then `Format('MyObject (%s)', [PointerString]);` – Jeroen Wiert Pluimers Feb 13 '13 at 22:58
  • @Jeroen, I can break when it occurs in my code. Please see my answer to user1008646 below. – Doug Feb 19 '13 at 15:50

2 Answers2

4

My best hypothesis is that your code is modifying some memory that it shouldn't, perhaps by dereferencing an uninitialized pointer. I've created a reproducible case that demonstrates this possibility. At least, it's reproducible on my machine with my version of the compiler. The exact same code might not do the same thing in another circumstance.

procedure TForm1.Button1Click(Sender: TObject);
var
  P : pbyte;
  S : string;
  T : ansistring;
begin
  // There's nothing special about HexDisplayPrefix.
  // It just happens to be one of the last global variables
  // declared in SysUtils.

  P := @ ( HexDisplayPrefix );

  // A few bytes beyond that is TwoHexLookUp.
  // This is a static array of unicode characters used by the IntToHex routine,
  // which is in turn used by Format when %p or %x are used.

  // I'll add an offset to P so that it points into that array.
  // I'll make the offset odd so that it points to the high byte of a character.
  // Of course, I can't guarantee that the same offset will work for you

  P := P + 5763;

  // Change the lookup table.
  // Of course, you would never do this on purpose.

  P ^ := 39;

  // Now let's call IntToHex

  S := IntToHex ( $E0, 2 );

  // Show the value on the screen.
  // Hey look, the zero has been replaced with a star.

  memo1 . lines . add ( S );

  // Convert the unicode string to an ansistring

  T := ansistring ( S );

  // Show the value on the screen.
  // When converting to Ansi, the system doesn't know what to do with the star,
  // so it replaces it with a question mark.

  memo1 . lines . add ( unicodestring(T) );

end;

Shows E with star and E with question mark

David Dubois
  • 3,842
  • 3
  • 18
  • 36
  • 1
    Yes, this makes sense, thanks for reproducing it. The overwrite would explain why this is giving me problems in totally different areas of my application. I can reproduce it in the same location by simply calling IntToHex with a specific fixed value like `s := IntToHex(2129827392, 8);`. I must be overwriting that memory, but the trick is now to figure out where it's happening. Do you have any tips for isolating it? – Doug Feb 19 '13 at 16:01
  • 1
    Start your program running, perhaps put a breakpoint on the first line of the code. View|Debug Windows|Breakpoints. Click the drop-down arrow beside the new breakpoint button. Select Data Breakpoint. For address enter TwoHexLookup. Size will default to 1024. OK to finish. Continue running your program. As soon as the table is changed, the breakpoint will fire and show you the line of code. I did this for the code above and the breakpoint immediately fired on the "P^:=39;" line. Good luck. – David Dubois Feb 20 '13 at 02:17
  • +1 and Thanks. It lead me to my answer guiding @Doug into steps to find the cause. – Jeroen Wiert Pluimers Feb 20 '13 at 08:41
2

As this appears to be a memory overwrite (cf. your comment to user1008646) you can try to follow these steps:

  1. First try to find out which memory address gets overwritten. You mention that s := IntToHex(2129827392, 8); fails. Find out the correct value, then find out if it is within TwoHexLookUp.
  2. If it is within TwoHexLookUp, then set a data-changed breakpoint (see How to define a breakpoint whenever an object field value changes? and Add data breakpoint on how to do this).
  3. Run your app until the breakpoint fires.

Ad 1: probably the easiest way is to look into TwoHexLookUp which value change has the same effect to get the wrong result from s := IntToHex(2129827392, 8); as you observe at run-time.

Thursday I'm doing some Delphi work at a client, so then I might have time to dig a bit deeper.

Edit
When you step through your process with F7, you indeed get into the SysInit first.

What you can do there is already set a breakpoint on the TwoHexLookup array.
Then either F9/F8/F7 (depending on the granularity you want) and keep an eye on the array in a Watch window. That should get you going.

Community
  • 1
  • 1
Jeroen Wiert Pluimers
  • 23,965
  • 9
  • 74
  • 154
  • The corruption begins at TwoHexLookup[227]. I can't reproduce this with a console app, and I can't reproduce it if I remove FastMM4 and madExcept from the uses statement of the .dpr file. However, the debugger shows the value as being corrupted even before those two units get initialized (and they are first in the uses statement - breakpoints in both unit initialization sections are not yet called). If I start by tracing into the app (F7), the first line the debugger shows is 1109 in SysInit.pas in`procedure _InitExe(InitTable: Pointer);` Very, very strange... – Doug Feb 19 '13 at 21:39
  • Just to clarify, I can't set a data breakpoint to find the problem due to the fact that the corruption is apparent when starting the app via F7. – Doug Feb 19 '13 at 21:49
  • I've accepted this as the answer, as described in my update to the original question. Thanks again for everyone's comments. – Doug Feb 19 '13 at 22:47
  • @Doug that SysInit is OK. See my edit in the answer that I will do shortly. – Jeroen Wiert Pluimers Feb 20 '13 at 08:38