Well, I decided to look into my suggestion for maintaining the call
stack of by "simply" tracking pairs of call/ret instructions. I've
decided it is either completely non-viable, or too complex to bother
with.
While a fine idea in theory, it relies on real code using call/return
and not playing too much with return address on the stack. I had
thought the main violations of the call/return rule would be some or all
of:
/* A - PIC code - dealt with already in vg_to_ucode */
call 1f
1: popl %reg
/* B - common idiom */
push code_addr
ret
/* C - manual call */
pop %reg
push some_addr
jmp *%reg
/* D - manual return */
pop %reg
jmp *%reg
Unfortunately I completely underestimated the twisty-turnyness of the
glibc dynamic linker, which basically does arbitrary stack manipulations
to manually build stack frames, manually call and manually return, and
also does delights such as:
xchg %eax, 8(%esp)
ret $8
In other words, to keep track of this stuff, Valgrind would have to keep
track of all stack accesses (optimistically accesses based off %esp;
pessimistically all memory accesses which happen to be in the area), and
maintain the call stack that way. I don't think this is viable.
A possibly more viable, but complex, approach is to use a hybrid
technique: maintain a call stack, and also walk up the call frames using
esp/ebp. If they agree, then all is well; if not, use the call stack to
resync the call frame walker, rather than using its information
directly. I think that would take rather more effort than I was
prepared to get slightly more precision in a backtrace, so I'm not going
to explore it for now.
J
|