|
From: Bryan M. <om...@br...> - 2006-08-28 11:09:23
|
Hi Folks, What versions of what compilers are we all using for x86 and x86-64? I have just re-written the tracking logic in Omega so that it's a little cleaner and takes more account of the ABI but to get quality reports (instead of waiting for some stray register to get overwritten 3 functions back up the stack), a little extra logic is required. The problem is that there is a fair bit of difference in the code generated by gcc 4.1.0 (with -m32) on my new x86-64 box and gcc 3.3.2 on my old x86 box and most of it, as my luck would have it, is around the handling of the stack pointer. Would it be acceptable to determine the compiler version at compile time and insert/remove code sections based upon it? With Omega living out of the Valgrind tree, everyone has to compile locally to use it anyway and if it gets to the point that a distro picks it up then I suppose all of their packages would be compiled with the same compiler version. As ever, any comments would be most welcome, Bryan "Brain Murders" Meredith |
|
From: Julian S. <js...@ac...> - 2006-08-28 11:30:35
|
> Would it be acceptable to determine the compiler version at compile time > and insert/remove code sections based upon it? I don't think that would be a robust assumption in practice. Not only that, but gcc isn't the only compiler in the universe; icc is quite widely used for one. What is it about the particular stack handling of different gccs that makes a difference for you? J |
|
From: Bryan M. <om...@br...> - 2006-08-28 12:58:34
|
Julian Seward wrote: >> Would it be acceptable to determine the compiler version at compile time >> and insert/remove code sections based upon it? > > I don't think that would be a robust assumption in practice. Not only > that, but gcc isn't the only compiler in the universe; icc is quite > widely used for one. > > What is it about the particular stack handling of different gccs that > makes a difference for you? > > J > Julian, I am aware of icc but don't have access to it - it would also have to be taken into account though. The main difference is the amount of stack manipulation that occurs: the old version tends to move the stack pointer around piecemeal as it needs to. The newer version moves the sp at the top of the function then typically leaves it alone. This is also evident in the use of push in the old verses mov (%esp) in the new. Looking at the two programs side by side, I think the real crux of it is the differing epilog code. I think I am falling over trying to detect when there is a value being returned through the accumulator. I need to know this as the accumulator should be ignored if it isn't being used to return anything, possibly generating a leak report at function exit. Is there a robust method of determining if a function returns a value (and in which register(s))? Bryan |
|
From: Julian S. <js...@ac...> - 2006-08-28 21:30:06
|
> Looking at the two programs side by side, I think the real crux of it is > the differing epilog code. I think I am falling over trying to detect > when there is a value being returned through the accumulator. This sounds like a dataflow/liveness problem. So, let me unwind one more level. Why do you want to know whether the accumulator contains a live vs dead value at the point the function returns? What are you going to do with that info? J |
|
From: Bryan M. <om...@br...> - 2006-08-28 21:57:25
|
Julian Seward wrote:
>> Looking at the two programs side by side, I think the real crux of it is
>> the differing epilog code. I think I am falling over trying to detect
>> when there is a value being returned through the accumulator.
>
> This sounds like a dataflow/liveness problem. So, let me unwind
> one more level. Why do you want to know whether the accumulator
> contains a live vs dead value at the point the function returns?
> What are you going to do with that info?
>
> J
>
Julian,
that's exactly the problem. If the accumulator holds a pointer to a
memory block and is live, it should be left alone and tracked out of the
function. If it is dead, it should be culled as the function exits,
possibly generating a leak report if it is the final pointer to that block.
If the pointer is dead but is left until it is over-written, the
location of the leak report is inaccurate.
The ability to detect the dead value allows function scope related
checking for leaks. As an example (this is scope2.c in the test directory):
#include <stdlib.h>
static void func1(void)
{
char *pointer = 0;
pointer = malloc(64); /* Line 7 */
return;
} /* Leak report Line 10 */
int main(int argc, char *argv[])
{
func1();
return 0; /* Line 16 */
}
At line 7, the malloc() call returns the pointer in the accumulator
which is then also saved into the stack variable "pointer". As the
function exits, the stack unwinds, removing the reference in "pointer"
but the accumulator will still hold a reference. If the accumulator is
detected as being dead, a leak report will be generated at line 10 as we
remove the reference it is holding and discover it is the last one. If
we don't invalidate the accumulator, we get the leak report at line 16
instead when the reference is overwritten by 0 in order to be returned
by main().
A report at line 16 is not only inferior to a report at line 10, it is
not going to provide the clarity that Omega should supply in it's goal
to assist in tracking down memory leaks: a report at line 10 makes it
pretty obvious that something went out of scope whilst a report at line
16 will leave most people scratching their heads.
It's pretty fundamental to the whole thing which is why I could do with
a robust method for determining when to cull registers on exit and when
to leave them alone - the ABI is simply not enough.
Hope that explains it,
Bryan
|
|
From: Josef W. <Jos...@gm...> - 2006-08-28 12:23:09
|
Hi Bryan, On Monday 28 August 2006 13:09, Bryan Meredith wrote: > What versions of what compilers are we all using for x86 and x86-64? I would say this very much depends on the user ;-) Of course I most often use the GCC which was installed with my distribution (GCC 4.1 on OpenSuse 10.1), but I also have the Intel compiler installed. This way, the binary is produced by ICC and the library code by GCC. But of course, when I want to use a given Valgrind tool, and there is a suggestion for the best compiler/options to use, I can arrange it when I want the tool to work perfectly. > Would it be acceptable to determine the compiler version at compile time > and insert/remove code sections based upon it? Why can't you detect the instrumentation you need at runtime? I would say that especially with valgrind, instrumentation can be much more dynamic depending on the environment than with any other instrumentation method. Josef |
|
From: Bryan M. <om...@br...> - 2006-08-28 13:03:10
|
Josef Weidendorfer wrote: > Hi Bryan, > > On Monday 28 August 2006 13:09, Bryan Meredith wrote: >> What versions of what compilers are we all using for x86 and x86-64? > > I would say this very much depends on the user ;-) > Of course I most often use the GCC which was installed with my distribution > (GCC 4.1 on OpenSuse 10.1), but I also have the Intel compiler installed. > This way, the binary is produced by ICC and the library code by GCC. > > But of course, when I want to use a given Valgrind tool, and there is > a suggestion for the best compiler/options to use, I can arrange it > when I want the tool to work perfectly. > >> Would it be acceptable to determine the compiler version at compile time >> and insert/remove code sections based upon it? > > Why can't you detect the instrumentation you need at runtime? > I would say that especially with valgrind, instrumentation > can be much more dynamic depending on the environment than with any other > instrumentation method. > Josef, I agree that would be the best solution but how does one work out the compiler, given the IR (or is it buried in the debug info somewhere). The other issue that you have highlighted is people having more than one compiler on their system, such that there are mixed code bases. See the reply to Julian as on reflection, this was truly a lousy idea and I need to dig further with what I have before I start doing anything really weird. Bryan |
|
From: Josef W. <Jos...@gm...> - 2006-08-28 17:07:31
|
On Monday 28 August 2006 15:03, Bryan Meredith wrote: > I agree that would be the best solution but how does one work out the > compiler, given the IR (or is it buried in the debug info somewhere). If there is full debug information, you should be able to extract everything you need (e.g. return type of a function). AFAIK, Valgrind currently does parse symbol tables and line debug info, but not much more. It would be cool to have a debug info parser for further info like type and variable name available for tools (for DWARF format). Regarding push/pop vs. mov(sp): It can be that this is an architecture optimization issue, and does change depending on command line options; ie. not really depending on the compiler itself - but perhaps the defaults for command line options have changed. Josef |
>>>>> "JW" == Josef Weidendorfer <Jos...@gm...> writes: JW> On Monday 28 August 2006 15:03, Bryan Meredith wrote: BM> I agree that would be the best solution but how does one work out BM> the compiler, given the IR (or is it buried in the debug info BM> somewhere). JW> If there is full debug information, you should be able to extract JW> everything you need (e.g. return type of a function). AFAIK, JW> Valgrind currently does parse symbol tables and line debug info, JW> but not much more. It would be cool to have a debug info parser JW> for further info like type and variable name available for tools JW> (for DWARF format). As I think I mentioned a while ago, one existing codebase that provides this is our research group's "Fjalar" framework, a melding of Valgrind and the GNU binutils "readelf" DWARF parser that we've used for a couple of fairly heavyweight analyses that combine Valgrind-based dynamic tracking with close-to-complete source level information from the debugging information. Its approach isn't quite as elegant as extending Valgrind's existing DWARF parser to get the additional information you need, but it scores high on the "laziness" scale. For more details: http://pag.csail.mit.edu/fjalar/ -- Stephen |
|
From: Bryan M. <om...@br...> - 2006-08-28 17:26:16
|
Josef Weidendorfer wrote: > On Monday 28 August 2006 15:03, Bryan Meredith wrote: >> I agree that would be the best solution but how does one work out the >> compiler, given the IR (or is it buried in the debug info somewhere). > > If there is full debug information, you should be able to extract everything > you need (e.g. return type of a function). AFAIK, Valgrind currently does > parse symbol tables and line debug info, but not much more. > It would be cool to have a debug info parser for further info like type and > variable name available for tools (for DWARF format). > > Regarding push/pop vs. mov(sp): It can be that this is an architecture > optimization issue, and does change depending on command line options; > ie. not really depending on the compiler itself - but perhaps the defaults > for command line options have changed. Josef, I did wonder about whether it was default settings in the gcc spec file but didn't investigate it: it's not something that I can change (my own, no problem - everybody else's, hmmmm) so I have to be able to deal with it. I would expect the information to be in the debug somewhere but getting to it in a sensible way is the key. Doing the test on Ijk_Return is definitely _when_ to do it though. I currently try to spot it by the pattern of register writes but it simply isn't robust enough and optimised code kills it completely. I shall investigate making this information available for lookup as we already have function names so it could go with them I suppose (I have some other ideas relating to extending the line number storage that would benefit anything that needs per line information to be stored (mainly coverage type stuff) so I have already been looking in that area). Bryan |
|
From: Bryan M. <om...@br...> - 2006-08-31 20:18:51
|
Julian,
looking in readdwarf.c and storage.c, I think I can put a patch together
that adds a flag into DiSym in order to indicate "no return", "return"
or "unknown" (for when the debug isn't there and it's time to hit a
fall-back method - this would also be the default value).
Would it be of interest? I don't want to spend time doing this if its
not going to go in (pending code review etc of course).
My idea is to extend the read_unitinfo_dwarf2 function by a) passing in
the seginfo pointer in order to give access to the symtab and b) to
parse out TAG_subprogram entries.
Basically, a void function has no type (AT_type) entry. Once a
subprogram is fully parsed, search_one_symtab can be used to find the
related DiSym and then update the entry from the default "unknown".
Bryan
Bryan Meredith wrote:
> Julian Seward wrote:
>>> Looking at the two programs side by side, I think the real crux of it is
>>> the differing epilog code. I think I am falling over trying to detect
>>> when there is a value being returned through the accumulator.
>> This sounds like a dataflow/liveness problem. So, let me unwind
>> one more level. Why do you want to know whether the accumulator
>> contains a live vs dead value at the point the function returns?
>> What are you going to do with that info?
>>
>> J
>>
>
> Julian,
>
> that's exactly the problem. If the accumulator holds a pointer to a
> memory block and is live, it should be left alone and tracked out of the
> function. If it is dead, it should be culled as the function exits,
> possibly generating a leak report if it is the final pointer to that block.
>
> If the pointer is dead but is left until it is over-written, the
> location of the leak report is inaccurate.
>
> The ability to detect the dead value allows function scope related
> checking for leaks. As an example (this is scope2.c in the test directory):
>
> #include <stdlib.h>
>
> static void func1(void)
> {
> char *pointer = 0;
>
> pointer = malloc(64); /* Line 7 */
>
> return;
> } /* Leak report Line 10 */
>
> int main(int argc, char *argv[])
> {
> func1();
>
> return 0; /* Line 16 */
> }
>
> At line 7, the malloc() call returns the pointer in the accumulator
> which is then also saved into the stack variable "pointer". As the
> function exits, the stack unwinds, removing the reference in "pointer"
> but the accumulator will still hold a reference. If the accumulator is
> detected as being dead, a leak report will be generated at line 10 as we
> remove the reference it is holding and discover it is the last one. If
> we don't invalidate the accumulator, we get the leak report at line 16
> instead when the reference is overwritten by 0 in order to be returned
> by main().
>
> A report at line 16 is not only inferior to a report at line 10, it is
> not going to provide the clarity that Omega should supply in it's goal
> to assist in tracking down memory leaks: a report at line 10 makes it
> pretty obvious that something went out of scope whilst a report at line
> 16 will leave most people scratching their heads.
>
> It's pretty fundamental to the whole thing which is why I could do with
> a robust method for determining when to cull registers on exit and when
> to leave them alone - the ABI is simply not enough.
>
> Hope that explains it,
> Bryan
>
> -------------------------------------------------------------------------
> Using Tomcat but need to do more? Need to support web services, security?
> Get stuff done quickly with pre-integrated technology to make your job easier
> Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
> http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
> _______________________________________________
> Valgrind-developers mailing list
> Val...@li...
> https://lists.sourceforge.net/lists/listinfo/valgrind-developers
>
|
|
From: Julian S. <js...@ac...> - 2006-09-01 13:18:28
|
I can see that some info like that would be helpful, but I'm concerned
that it might be a case of fixing the symptoms (and/or adding a fix
which makes sense only for eg x86 and not generally).
What I think would be helpful is to make a concise, semi-formal
statement of the problem. I think this would help by showing how this
relates to standard compiler concepts of liveness and reachability.
In particular I'd like to find a solution which gives accurate results
and which works well even in the presence of optimised code, which AIUI
Omega currently doesn't.
My initial thoughts at such a statement are:
- keep track of all allocated blocks
- keep track of all potential roots. Potential roots at any
time are
* the words in all currently accessible memory, plus the words
in the registers
BUT
only those for which the next event is a read
- If some part of the address space disappears, that can be thought of
as a write.
- If a write (to a potential root) happens, and that destroys the last
pointer to a particular block, then we can complain at that point
of a leak.
- However, that may be too late. (as per current example)
What we really want to find is something along the lines of
'dead writes' (to registers and memory). A dead write puts a value
into that storage location which is never read, only overwritten
(or the storage location goes out of scope, which is equivalent).
Perhaps it should be that a leak is reported at a dead write of
the last pointer to a block. Or something. Anyway, the general
idea is: if you can characterise the problem in terms of dataflow
and liveness, perhaps it becomes possible to build an implementation
which is robust to ABI changes, compiler optimisation, and across
different platforms.
The above ideas are half-baked; don't take the details too literally.
---
Another thing that would help (perhaps the same thing, really)
is to give a very precise specification of what the tool is intended
to do.
Originally I had the impression that it was "find where the last pointer
to block X is overwritten"; but when looked at under a magnifying glass
it's clear this can lead to error reports which point to some location
in the code flow which may be arbitrarily far after the place where you
really wanted to report the error. So (and I think this is the crux
of the problem) how do you precisely specify those program points where
you *do* want to report an error?
J
On Thursday 31 August 2006 21:18, Bryan Meredith wrote:
> Julian,
>
> looking in readdwarf.c and storage.c, I think I can put a patch together
> that adds a flag into DiSym in order to indicate "no return", "return"
> or "unknown" (for when the debug isn't there and it's time to hit a
> fall-back method - this would also be the default value).
>
> Would it be of interest? I don't want to spend time doing this if its
> not going to go in (pending code review etc of course).
>
> My idea is to extend the read_unitinfo_dwarf2 function by a) passing in
> the seginfo pointer in order to give access to the symtab and b) to
> parse out TAG_subprogram entries.
>
> Basically, a void function has no type (AT_type) entry. Once a
> subprogram is fully parsed, search_one_symtab can be used to find the
> related DiSym and then update the entry from the default "unknown".
>
> Bryan
>
> Bryan Meredith wrote:
> > Julian Seward wrote:
> >>> Looking at the two programs side by side, I think the real crux of it
> >>> is the differing epilog code. I think I am falling over trying to
> >>> detect when there is a value being returned through the accumulator.
> >>
> >> This sounds like a dataflow/liveness problem. So, let me unwind
> >> one more level. Why do you want to know whether the accumulator
> >> contains a live vs dead value at the point the function returns?
> >> What are you going to do with that info?
> >>
> >> J
> >
> > Julian,
> >
> > that's exactly the problem. If the accumulator holds a pointer to a
> > memory block and is live, it should be left alone and tracked out of the
> > function. If it is dead, it should be culled as the function exits,
> > possibly generating a leak report if it is the final pointer to that
> > block.
> >
> > If the pointer is dead but is left until it is over-written, the
> > location of the leak report is inaccurate.
> >
> > The ability to detect the dead value allows function scope related
> > checking for leaks. As an example (this is scope2.c in the test
> > directory):
> >
> > #include <stdlib.h>
> >
> > static void func1(void)
> > {
> > char *pointer = 0;
> >
> > pointer = malloc(64); /* Line 7 */
> >
> > return;
> > } /* Leak report Line 10 */
> >
> > int main(int argc, char *argv[])
> > {
> > func1();
> >
> > return 0; /* Line 16 */
> > }
> >
> > At line 7, the malloc() call returns the pointer in the accumulator
> > which is then also saved into the stack variable "pointer". As the
> > function exits, the stack unwinds, removing the reference in "pointer"
> > but the accumulator will still hold a reference. If the accumulator is
> > detected as being dead, a leak report will be generated at line 10 as we
> > remove the reference it is holding and discover it is the last one. If
> > we don't invalidate the accumulator, we get the leak report at line 16
> > instead when the reference is overwritten by 0 in order to be returned
> > by main().
> >
> > A report at line 16 is not only inferior to a report at line 10, it is
> > not going to provide the clarity that Omega should supply in it's goal
> > to assist in tracking down memory leaks: a report at line 10 makes it
> > pretty obvious that something went out of scope whilst a report at line
> > 16 will leave most people scratching their heads.
> >
> > It's pretty fundamental to the whole thing which is why I could do with
> > a robust method for determining when to cull registers on exit and when
> > to leave them alone - the ABI is simply not enough.
> >
> > Hope that explains it,
> > Bryan
> >
> > -------------------------------------------------------------------------
> > Using Tomcat but need to do more? Need to support web services, security?
> > Get stuff done quickly with pre-integrated technology to make your job
> > easier Download IBM WebSphere Application Server v.1.0.1 based on Apache
> > Geronimo
> > http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
> > _______________________________________________
> > Valgrind-developers mailing list
> > Val...@li...
> > https://lists.sourceforge.net/lists/listinfo/valgrind-developers
|
|
From: Bryan M. <om...@br...> - 2006-09-02 20:49:20
|
Julian,
I had an idea while writing this reply (6 odd hours ago) and as a
result, I have actually solved it, tested it on both boxes (in 32bit and
64bit mode on my Athlon64 box) and am just tidying up a couple of bits
and bobs before posting Beta05! :D
for the record:
Problem:
The accumulator contains the final live pointer to a memory block on
return from a function but should actually die at that point as the
function does not in fact return a value (a 'C' void type function).
Eventual overwrite of the value in the accumulator produces a stack
trace far from the actual point where the leak should have been reported.
Solution:
On function exit, note that the accumulator is the only live pointer to
that memory block and store a stack trace that references the end of the
function. When the accumulator is overwritten, note that it matches the
last live reference we stored earlier then use the stack trace that was
stored earlier in the leak report(s).
If another reference is added to the block (it was a return value after
all), clear the stored information so that any future leak can be
reported normally.
In the case of nested calls, we do not overwrite the cached stack trace
because we confirm that the accumulator is still the only live reference
so leave it pointing to the original "actual leak" position.
I have left some of the comments in below as I think it useful to have
them on record, should anyone come along asking questions about stuff
like optimised code or support or more usefully, what the test cases are
looking for and why.
Bryan
Julian Seward wrote:
> I can see that some info like that would be helpful, but I'm concerned
> that it might be a case of fixing the symptoms (and/or adding a fix
> which makes sense only for eg x86 and not generally).
I agree - that was the main reason for checking before going ahead.
Whilst it would partially solve my problems, there is undetermined
impact in other areas.
>
> What I think would be helpful is to make a concise, semi-formal
> statement of the problem. I think this would help by showing how this
> relates to standard compiler concepts of liveness and reachability.
> In particular I'd like to find a solution which gives accurate results
> and which works well even in the presence of optimised code, which AIUI
> Omega currently doesn't.
The main problem with optimised code is generating any sort of useful
information to clue the user into just where things are happening.
Tracking is not the problem (although there are some issues that I would
like to clean up when the un-optimised case is correct), its the lack of
correspondence to the original source code flow. You know what it's like
trying to debug optimised code when you single step and the debugger
does anything but on the source display, leaping up and down all over
the place. Function in-lining and tail calls are also a severe problem
as it makes function scope almost impossible to determine.
>
> My initial thoughts at such a statement are:
>
> - keep track of all allocated blocks
>
> - keep track of all potential roots. Potential roots at any
> time are
>
> * the words in all currently accessible memory, plus the words
> in the registers
> BUT
> only those for which the next event is a read
Omega handles a specialised case of this in order to cope with a common
program idiom. From the web page:
----------------------------------------------------
Lets say you have something along the lines of this:
1 secret *foo = (secret *)malloc(sizeof(bar) + sizeof(secret) +
alignment_correction);
2 foo->secret_stuff = magic_key;
3 etc.
4 foo++;
5 return (bar*)foo;
Internally, Omega uses shadow blocks to track references within an
allocated block. Thus, when you increase "foo" on line 4, Omega creates
a shadow block that ties back to the main block allocated at line 1
instead of raising a leak report.
----------------------------------------------------
Without this special case handling, Omega produces meaningless results
in the presence of any custom allocation.
>
> - If some part of the address space disappears, that can be thought of
> as a write.
>
> - If a write (to a potential root) happens, and that destroys the last
> pointer to a particular block, then we can complain at that point
> of a leak.
>
> - However, that may be too late. (as per current example)
>
> What we really want to find is something along the lines of
> 'dead writes' (to registers and memory). A dead write puts a value
> into that storage location which is never read, only overwritten
> (or the storage location goes out of scope, which is equivalent).
>
> Perhaps it should be that a leak is reported at a dead write of
> the last pointer to a block. Or something. Anyway, the general
> idea is: if you can characterise the problem in terms of dataflow
> and liveness, perhaps it becomes possible to build an implementation
> which is robust to ABI changes, compiler optimisation, and across
> different platforms.
>
> The above ideas are half-baked; don't take the details too literally.
They are actually pretty much all of it...
>
> ---
>
> Another thing that would help (perhaps the same thing, really)
> is to give a very precise specification of what the tool is intended
> to do.
>
> Originally I had the impression that it was "find where the last pointer
> to block X is overwritten"; but when looked at under a magnifying glass
> it's clear this can lead to error reports which point to some location
> in the code flow which may be arbitrarily far after the place where you
> really wanted to report the error. So (and I think this is the crux
> of the problem) how do you precisely specify those program points where
> you *do* want to report an error?
>
> J
>
When a block is allocated (through malloc(), realloc() etc), a pointer
is returned to the calling program. Omega tracks this pointer and any
copies that are created and destroyed until such time that the block is
free()ed (through free() or realloc() with a bigger block size) or the
last pointer is lost, either by being overwritten or the containing
storage going out of scope or "dying".
In the Omega source are a number of small test cases that demonstrate
the classes of situation where Omega should report a useful message for
the user. They fall into 3 main categories:
Block
Overwrite
Scope
Block
-----
These tests (and functionality) deal with pointers stored in other heap
allocated blocks ie. the pointer to allocated block B is stored within
allocated block A. The lifetime of the pointer to block B (assuming it
is never overwritten) is determined by the lifetime of block A. If block
A is returned to the OS or leaks, the pointer to block B dies at this
point. If this is the last pointer to block B, then block B leaks and so
on. This functionality is quite simple as it involves death of storage
and little else. Typically, if a leak occurs it will be within the
free() function because some malloc()ed structure has pointers to other
malloc()ed structures and they weren't cleaned up properly before the
base structure was free()ed. Singly linked lists can generate long
chains of leaks if you overwrite the head pointer.
Overwrite
---------
These tests deal with the destruction of the pointer itself, rather than
the lifetime of the storage where the pointer resides. The simplest case
is overwriting a pointer (in memory or a register) with another value
(ie. NULL). If the last pointer to a block is overwritten then a leak of
that block occurs at that point. There is a specialisation of this
namely the shadow block stuff mentioned inline above.
Scope
-----
These tests deal with the death of storage (the Block section above is a
specific form of this that deals with heap allocated storage). The
non-heap storage is either stack or register based. Automatic variables
live on the stack and as such, when the function exits and the stack
pointer is moved, their storage dies. Any pointers within automatic
variables are therefore lost when the stack unwinds generating a leak
report on the last line of the function. Seeing a report here is a sure
sign that your automatic variable went out of scope holding a pointer
that you didn't stash somewhere else or free().
(**next bit written just as the brain-wave hit**)
The other part of scope, and this is the bit that is currently standing
in the way of a full blown release candidate, is the scope of registers.
The ABI for each architecture (I can only cover x86 and x86_64 here -
whilst I don't actually know x86 assembler, I have never played with
Power Architecture assembler in my life) gives a preferred use or
purpose for each of the registers and also details function entry and
exit along with parameter passing in each direction. So, as a function
returns and the stack unwinds, any register that is described in the ABI
as belonging to the called function should now be classed as dead. This
is fine and works well apart from a single fly in the ointment - the
accumulator can optionally be used to return a value of some description
to the calling function. The problem is that there is no way to easily
determine if a function is going to return a value in the accumulator or
not. The accumulator is a scratch register so could well contain a valid
pointer that should not in fact survive past the end of the function.
scope2.c in the tests directory looks like this:
----------------------------------------------------
#include <stdlib.h>
static void func1(void)
{
char *pointer = 0;
pointer = malloc(64); /* Line 7 */
return;
} /* Leak report Line 10 */
int main(int argc, char *argv[])
{
func1();
return 0;
}
----------------------------------------------------
If you debug this program in mixed source/assembler, it is clear that at
the exit of func1(), the accumulator holds the value returned by
malloc(64) and also stored in the variable "pointer". Whilst the storage
for the variable dies as the stack unwinds, we cannot easily tell from
the assembler whether func1() is returning the value in the accumulator
or not. Ideally, the leak report should occur at line 10. If we do not
invalidate the accumulator, the leak report will occur at line 15 where
main() set's it's return value, finally overwriting the accumulator.
|
|
From: Nicholas N. <nj...@cs...> - 2006-09-02 22:44:09
|
On Sat, 2 Sep 2006, Bryan Meredith wrote: > I had an idea while writing this reply (6 odd hours ago) and as a > result, I have actually solved it, tested it on both boxes (in 32bit and > 64bit mode on my Athlon64 box) and am just tidying up a couple of bits > and bobs before posting Beta05! :D Nice :) You've discovered how annoying and confusing real programs can be at the binary level. Coming up with robust ways to do things in Valgrind tools is often a real challenge. The best case I can think of is function wrapping, which sound easy in theory but in practice took Julian and I about 5 abortive attempts before Julian worked out a satisfactory approach. Nick |