| |
 |
|
|
Linux Forum Index » Linux Development - System » interposing library calls - general development...
Page 2 of 2 Goto page Previous 1, 2
|
| Author |
Message |
| Bernie Ohls... |
Posted: Sun Jun 29, 2008 12:56 pm |
|
|
|
Guest
|
phil-news-nospam at (no spam) ipal.net wrote:
Quote: I'm looking for any documents on developing interposing calls for libraries,
particular for libc, but possibly for any library.
I have been down this same road, using interposition to develop
production-quality SW. Unfortunately, though interposition is actually
quite stable, powerful, and (nowadays) pretty portable, it is still
generally used only as a one-off debugging aid or as a workaround for a
bug in a particular OS or app (typically something like overriding the
uname() call to allow a program to run on a system it wasn't designed
for). There appears to be no document describing how to use it in a
"grown-up" way wrt portability, thread-safety and re-entrancy, avoiding
deadlocks in intra-library calls, etc.
In particular I know of no reliable, automated way of determining which
symbols need to be intercepted. But these things can generally be worked
out via nm and elfdump and so on, and an individual platform's ABI
commitments usually ensure that they won't change (additions are a
different story of course).
So I hope you or someone finds and posts such a document but I don't
hold out much hope. In the meantime I would recommend the Sun Linker and
Libraries Guide (http://docs.sun.com/app/docs/doc/817-1984). Although it
only speaks officially for Sun, it's really about ELF/SysV linking and
most other extant systems have worked from the same spec. Linux and *BSD
for sure.
My situation has been simplified by the fact that I'm not interested in
intra- or inter-library calls but only in explicit calls from the
application. In fact ignoring those library calls can be a feature. For
instance, thinking of opened files, using truss or strace reveals a
number of file opens done by libraries which you may find uninteresting
(such as locale files). Sticking to the app layer ignores all that stuff
and tells us only what the application does explicitly. In other words
it reveals more about the app logic and less about the system
implementation. But interposition can also be used to grab those calls
if you choose to.
You've said that you don't want simple examples and tips but I will
throw in a couple:
1. There's a real risk of deadlock of course; even reporting an error
can lock up if it uses one of the symbols you're interposing on. I've
had pretty good luck playing games with the C preprocessor here. I.e.
interpose on "open" to get a symbol "my_open", then "#define open
my_open". This way any code doing a simple-minded 'open("foo")' won't
get into trouble. Otherwise it becomes mind-bending trying to keep track
of what symbol means what when. For a while I was actually interposing
on dlopen() - that led to some situations which made the old bean hurt.
2. Despite what others have said, process creation is painful. There's
no problem with fork() as long as you're careful. The problem is vfork.
Here's a quote from the Sun man page:
The vfork() function can normally be used the same way as fork().
The procedure that called vfork(), however, should not return
while running in the child's context, since the eventual return
from vfork() would be to a stack frame that no longer exists.
You see the problem; it's impossible to wrap vfork since the wrapper is
not allowed to return. I've "solved" this by intercepting vfork() calls
and turning them into a real fork(). This works most of the time but is
not ideal. On a given platform and CPU it may be possible to work around
this with handcrafted assembler code but I haven't tried yet. If you
solve this I'd appreciate it if you'd post the solution.
Bernie O. |
|
|
| Back to top |
|
| John Reiser... |
Posted: Sun Jun 29, 2008 2:42 pm |
|
|
|
Guest
|
Bernie Ohls wrote:
Quote: The problem is vfork.
[snip]
it's impossible to wrap vfork since the wrapper is
not allowed to return. I've "solved" this by intercepting vfork() calls
and turning them into a real fork(). This works most of the time but is
not ideal. On a given platform and CPU it may be possible to work around
this with handcrafted assembler code but I haven't tried yet. If you
solve this I'd appreciate it if you'd post the solution.
You already discovered the solution: replace vfork with fork.
Everyone has agreed that vfork is a performance optimization only.
If replacing vfork with real fork creates any semantic difference,
then that is a certified bug in the code which [mis-]uses vfork.
-- |
|
|
| Back to top |
|
| David Schwartz... |
Posted: Sun Jun 29, 2008 3:17 pm |
|
|
|
Guest
|
On Jun 27, 3:09 pm, phil-news-nos... at (no spam) ipal.net wrote:
Quote: My interposing is meant to be transparent production. That is, once it is
implemented and deployed, it will be left in place. It is not for debugging,
but that might be done as well for other things.
Then I wouldn't use the method that 'ltrace' uses. I would use the
dlsym LD_PRELOAD mechanism discussed elsewhere. The 'ltrace' mechanism
is not really suitable for a transparent production use.
DS |
|
|
| Back to top |
|
| Bernie Ohls... |
Posted: Sun Jun 29, 2008 10:43 pm |
|
|
|
Guest
|
John Reiser wrote:
Quote: Bernie Ohls wrote:
The problem is vfork.
[snip]
it's impossible to wrap vfork since the wrapper is
not allowed to return. I've "solved" this by intercepting vfork() calls
and turning them into a real fork(). This works most of the time but is
not ideal. On a given platform and CPU it may be possible to work around
this with handcrafted assembler code but I haven't tried yet. If you
solve this I'd appreciate it if you'd post the solution.
You already discovered the solution: replace vfork with fork.
Everyone has agreed that vfork is a performance optimization only.
If replacing vfork with real fork creates any semantic difference,
then that is a certified bug in the code which [mis-]uses vfork.
There's a lot of history around vfork, in c.u.p as well as other places.
I don't want to go over it, and it's not topical here anyway, but the
short version is that when a system is running low on virtual memory a
vfork/exec sequence might succeed where a fork/exec would fail. This is
why I say my solution is not ideal. Admittedly it's a corner case.
Bernie O. |
|
|
| Back to top |
|
| Rainer Weikusat... |
Posted: Mon Jun 30, 2008 1:32 am |
|
|
|
Guest
|
Bernie Ohls <Bernie.Ohls at (no spam) no.mail> writes:
Quote: John Reiser wrote:
Bernie Ohls wrote:
The problem is vfork.
[snip]
it's impossible to wrap vfork since the wrapper is
not allowed to return. I've "solved" this by intercepting vfork() calls
and turning them into a real fork(). This works most of the time but is
not ideal. On a given platform and CPU it may be possible to work around
this with handcrafted assembler code but I haven't tried yet. If you
solve this I'd appreciate it if you'd post the solution.
You already discovered the solution: replace vfork with fork.
Everyone has agreed that vfork is a performance optimization only.
If replacing vfork with real fork creates any semantic difference,
then that is a certified bug in the code which [mis-]uses vfork.
There's a lot of history around vfork, in c.u.p as well as other
places. I don't want to go over it, and it's not topical here anyway,
but the short version is that when a system is running low on virtual
memory a vfork/exec sequence might succeed where a fork/exec would
fail.
Assuming the system is really 'running low on virtual memory', it is
conceivable that vfork succeeds, but exec fails :->. |
|
|
| Back to top |
|
| ... |
Posted: Mon Jun 30, 2008 10:22 am |
|
|
|
Guest
|
In comp.unix.programmer Bernie Ohls <Bernie.Ohls at (no spam) no.mail> wrote:
| You've said that you don't want simple examples and tips but I will
| throw in a couple:
Thoe documents that only have simple examples tend to be incomplete with
respect to the whole picture that I'm looking for a single document to
explain. But, a collection of simple examples would be good towards me
writing such a document. Given that I have not found a single big
picture document, it looks like this is what I will have to work with.
| 1. There's a real risk of deadlock of course; even reporting an error
| can lock up if it uses one of the symbols you're interposing on. I've
| had pretty good luck playing games with the C preprocessor here. I.e.
| interpose on "open" to get a symbol "my_open", then "#define open
| my_open". This way any code doing a simple-minded 'open("foo")' won't
| get into trouble. Otherwise it becomes mind-bending trying to keep track
| of what symbol means what when. For a while I was actually interposing
| on dlopen() - that led to some situations which made the old bean hurt.
I can see where that would be painful.
| 2. Despite what others have said, process creation is painful. There's
| no problem with fork() as long as you're careful. The problem is vfork.
| Here's a quote from the Sun man page:
|
| The vfork() function can normally be used the same way as fork().
| The procedure that called vfork(), however, should not return
| while running in the child's context, since the eventual return
| from vfork() would be to a stack frame that no longer exists.
|
| You see the problem; it's impossible to wrap vfork since the wrapper is
| not allowed to return. I've "solved" this by intercepting vfork() calls
| and turning them into a real fork(). This works most of the time but is
| not ideal. On a given platform and CPU it may be possible to work around
| this with handcrafted assembler code but I haven't tried yet. If you
| solve this I'd appreciate it if you'd post the solution.
Since vfork() is not supposed to return to the parent until the child calls
execve() or _exit(), it might seem like vfork() can be wrapped. The problem
is that the wrapped context exists in the child, too. When the child returns
it modifies the stack frame both child and parent share. And the child does
need to have the wrapper return since the program will be coded under the
assumption it will be doing the execve() call after vfork(). The solution
I see is very non-portable, which requires the wrapper to manipulate the
stack frame and make a copy before the systcall, and restore it afterwards.
But who knows what all the pitfalls of this will be.
If I write the big picture document, I'll try to describe why vfork() is one
of the syscall stubs that should not be interposed by function call wrapping.
--
|WARNING: Due to extreme spam, googlegroups.com is blocked. Due to ignorance |
| by the abuse department, bellsouth.net is blocked. If you post to |
| Usenet from these places, find another Usenet provider ASAP. |
| Phil Howard KA9WGN (email for humans: first name in lower case at ipal.net) | |
|
|
| Back to top |
|
| ... |
Posted: Mon Jun 30, 2008 10:25 am |
|
|
|
Guest
|
In comp.unix.programmer Rainer Weikusat <rweikusat at (no spam) mssgmbh.com> wrote:
| Bernie Ohls <Bernie.Ohls at (no spam) no.mail> writes:
|> John Reiser wrote:
|>> Bernie Ohls wrote:
|>>
|>>> The problem is vfork.
|>> [snip]
|>>> it's impossible to wrap vfork since the wrapper is
|>>> not allowed to return. I've "solved" this by intercepting vfork() calls
|>>> and turning them into a real fork(). This works most of the time but is
|>>> not ideal. On a given platform and CPU it may be possible to work around
|>>> this with handcrafted assembler code but I haven't tried yet. If you
|>>> solve this I'd appreciate it if you'd post the solution.
|>> You already discovered the solution: replace vfork with fork.
|>> Everyone has agreed that vfork is a performance optimization only.
|>> If replacing vfork with real fork creates any semantic difference,
|>> then that is a certified bug in the code which [mis-]uses vfork.
|>
|> There's a lot of history around vfork, in c.u.p as well as other
|> places. I don't want to go over it, and it's not topical here anyway,
|> but the short version is that when a system is running low on virtual
|> memory a vfork/exec sequence might succeed where a fork/exec would
|> fail.
|
| Assuming the system is really 'running low on virtual memory', it is
| conceivable that vfork succeeds, but exec fails :->.
Or if it is even lower on virtual memory, everything can fail. I'd call it
more of a boundary case than a corner case. Correct programs can fail for
reasons like this. They simply need to handle errors in as correct a way
as possible.
--
|WARNING: Due to extreme spam, googlegroups.com is blocked. Due to ignorance |
| by the abuse department, bellsouth.net is blocked. If you post to |
| Usenet from these places, find another Usenet provider ASAP. |
| Phil Howard KA9WGN (email for humans: first name in lower case at ipal.net) | |
|
|
| Back to top |
|
| Paul Pluzhnikov... |
Posted: Sat Jul 05, 2008 10:02 pm |
|
|
|
Guest
|
phil-news-nospam at (no spam) ipal.net writes:
Quote: In comp.unix.programmer John Reiser <jreiser at (no spam) bitwagon.com> wrote:
....
| glibc also has symbols such as:
| 3785: 005eb32c 205 FUNC LOCAL HIDDEN 11 __GI___open64
| 4354: 005eb2b0 122 FUNC LOCAL DEFAULT 11 __GI_open
....
Potentially a need to interpose every open could interpose these other
function entry points as well.
Except you can't interpose them without "extreme hackery": the
symbols are local, which means they don't go through "normal"
lazy binding. Rather, there are direct 'call 0x5eb2b0' instructions
inside glibc, and if you want to "steal" them, you have to patch
..text, or place breakpoints (ala "ltrace").
Quote: It is issues like these I'm wanting to read about. And if that means the
solution ultimately needs to be an OS syscall level intercept like ptrace
in Linux, than I think the document would need to cover that, too (and how
on at least a few major platforms like BSD, Linux, and Solaris).
I have been doing large amount of libc interposing on Linux, Solaris,
HP-UX and AIX for the last 10 years, and have not seen a document
you are seeking.
From my experience, the issues you are likely to encounter are:
1. Partial interception (e.g. interposing mmap() does not get you
intra-glibc calls to mmap() from e.g. fopen()).
This is the issue John raised with open(); there are many more
such "internal" calls in glibc, and some in Solaris libc as well.
2. Startup dependencies: libc startup is a complicated dance,
where things work only partially while libc is starting itself up,
and calling things in the "wrong" order often leads to a crash
either due to NULL pointer dereference (some global pointer
is expected to have been set up, but hasn't been set yet),
or infinite recursion (discover that some global pointer hasn't
been set up yet, try to set it, hit interposer, discover that
the global pointer hasn't been set up yet, spin into the ground ...).
In addition, libc startup of threaded programs is even more
complicated, as various pthread_* routines expect parts of libc
they depend on to have been initialized beforehand, and
interposing can severely change the order of initialization.
3. Interposing symbols that are called during dlsym(): on Linux,
for programs linked against libpthread, dlsym() calls calloc().
If you interpose calloc(), you can't find the "original" glibc
calloc() via 'dlsym(RTLD_NEXT, "calloc")' because that will
result in infinite recursion.
4. Symbol versioning: there are two incompatible versions of
e.g. pthread_cond_init() inside glibc, and if you have 3rd
party libraries in the picture, it's quite possible for both of
these versions to be actually used. Correctly interposing both
symbols requires some tricks, and the use of dlvsym().
Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email. |
|
|
| Back to top |
|
| ... |
Posted: Mon Jul 07, 2008 4:28 pm |
|
|
|
Guest
|
In comp.unix.programmer Paul Pluzhnikov <ppluzhnikov-nsp at (no spam) gmail.com> wrote:
| phil-news-nospam at (no spam) ipal.net writes:
|
|> In comp.unix.programmer John Reiser <jreiser at (no spam) bitwagon.com> wrote:
| ...
|> | glibc also has symbols such as:
|> | 3785: 005eb32c 205 FUNC LOCAL HIDDEN 11 __GI___open64
|> | 4354: 005eb2b0 122 FUNC LOCAL DEFAULT 11 __GI_open
| ...
|> Potentially a need to interpose every open could interpose these other
|> function entry points as well.
|
| Except you can't interpose them without "extreme hackery": the
| symbols are local, which means they don't go through "normal"
| lazy binding. Rather, there are direct 'call 0x5eb2b0' instructions
| inside glibc, and if you want to "steal" them, you have to patch
| .text, or place breakpoints (ala "ltrace").
Ah, yes, they would be statically linked within the linking of the
library itself. So this means that library interposing might have
to be limited to API scope. Beyond that is ptrace() intercepting
(or whatever is equivalent to that Linux call on other systems).
|> It is issues like these I'm wanting to read about. And if that means the
|> solution ultimately needs to be an OS syscall level intercept like ptrace
|> in Linux, than I think the document would need to cover that, too (and how
|> on at least a few major platforms like BSD, Linux, and Solaris).
|
| I have been doing large amount of libc interposing on Linux, Solaris,
| HP-UX and AIX for the last 10 years, and have not seen a document
| you are seeking.
OK. I've pretty much ended the search, anyway.
| From my experience, the issues you are likely to encounter are:
|
| 1. Partial interception (e.g. interposing mmap() does not get you
| intra-glibc calls to mmap() from e.g. fopen()).
|
| This is the issue John raised with open(); there are many more
| such "internal" calls in glibc, and some in Solaris libc as well.
One would have to be wanting to interpose only what the application
is directly calling for this to be useful or clean, then.
| 2. Startup dependencies: libc startup is a complicated dance,
| where things work only partially while libc is starting itself up,
| and calling things in the "wrong" order often leads to a crash
| either due to NULL pointer dereference (some global pointer
| is expected to have been set up, but hasn't been set yet),
| or infinite recursion (discover that some global pointer hasn't
| been set up yet, try to set it, hit interposer, discover that
| the global pointer hasn't been set up yet, spin into the ground ...).
|
| In addition, libc startup of threaded programs is even more
| complicated, as various pthread_* routines expect parts of libc
| they depend on to have been initialized beforehand, and
| interposing can severely change the order of initialization.
|
| 3. Interposing symbols that are called during dlsym(): on Linux,
| for programs linked against libpthread, dlsym() calls calloc().
|
| If you interpose calloc(), you can't find the "original" glibc
| calloc() via 'dlsym(RTLD_NEXT, "calloc")' because that will
| result in infinite recursion.
A new call to calloc() via the interposers call ... a nice gotcha to
know about. One would have to be sure they are not interposing the
library itself (e.g. calls inside the library don't get interposed).
Not sure how that is done.
| 4. Symbol versioning: there are two incompatible versions of
| e.g. pthread_cond_init() inside glibc, and if you have 3rd
| party libraries in the picture, it's quite possible for both of
| these versions to be actually used. Correctly interposing both
| symbols requires some tricks, and the use of dlvsym().
|
| Cheers,
These are the kinds of things I was looking for. Thanks for the starter clues.
--
|WARNING: Due to extreme spam, googlegroups.com is blocked. Due to ignorance |
| by the abuse department, bellsouth.net is blocked. If you post to |
| Usenet from these places, find another Usenet provider ASAP. |
| Phil Howard KA9WGN (email for humans: first name in lower case at ipal.net) | |
|
|
| Back to top |
|
| ... |
Posted: Tue Jul 29, 2008 9:55 am |
|
|
|
Guest
|
On topics of this month restored
phil-news-nos... at (no spam) ipal.net wrote:
Quote: In comp.unix.programmer Paul Pluzhnikov <ppluzhnikov-nsp at (no spam) gmail.com> wrote:
| phil-news-nospam at (no spam) ipal.net writes:
|
|> In comp.unix.programmer John Reiser <jreiser at (no spam) bitwagon.com> wrote:
| ...
|> | glibc also has symbols such as:
|> | 3785: 005eb32c 205 FUNC LOCAL HIDDEN 11 __GI___open64
|> | 4354: 005eb2b0 122 FUNC LOCAL DEFAULT 11 __GI_open
| ...
|> Potentially a need to interpose every open could interpose these other
|> function entry points as well.
|
| Except you can't interpose them without "extreme hackery": the
| symbols are local, which means they don't go through "normal"
| lazy binding. Rather, there are direct 'call 0x5eb2b0' instructions
| inside glibc, and if you want to "steal" them, you have to patch
| .text, or place breakpoints (ala "ltrace").
Ah, yes, they would be statically linked within the linking of the
library itself. So this means that library interposing might have
to be limited to API scope. Beyond that is ptrace() intercepting
(or whatever is equivalent to that Linux call on other systems).
|> It is issues like these I'm wanting to read about. And if that means the
|> solution ultimately needs to be an OS syscall level intercept like ptrace
|> in Linux, than I think the document would need to cover that, too (and how
|> on at least a few major platforms like BSD, Linux, and Solaris).
|
| I have been doing large amount of libc interposing on Linux, Solaris,
| HP-UX and AIX for the last 10 years, and have not seen a document
| you are seeking.
OK. I've pretty much ended the search, anyway.
| From my experience, the issues you are likely to encounter are:
|
| 1. Partial interception (e.g. interposing mmap() does not get you
| intra-glibc calls to mmap() from e.g. fopen()).
|
| This is the issue John raised with open(); there are many more
| such "internal" calls in glibc, and some in Solaris libc as well.
One would have to be wanting to interpose only what the application
is directly calling for this to be useful or clean, then.
| 2. Startup dependencies: libc startup is a complicated dance,
| where things work only partially while libc is starting itself up,
| and calling things in the "wrong" order often leads to a crash
| either due to NULL pointer dereference (some global pointer
| is expected to have been set up, but hasn't been set yet),
| or infinite recursion (discover that some global pointer hasn't
| been set up yet, try to set it, hit interposer, discover that
| the global pointer hasn't been set up yet, spin into the ground ...).
|
| In addition, libc startup of threaded programs is even more
| complicated, as various pthread_* routines expect parts of libc
| they depend on to have been initialized beforehand, and
| interposing can severely change the order of initialization.
|
| 3. Interposing symbols that are called during dlsym(): on Linux,
| for programs linked against libpthread, dlsym() calls calloc().
|
| If you interpose calloc(), you can't find the "original" glibc
| calloc() via 'dlsym(RTLD_NEXT, "calloc")' because that will
| result in infinite recursion.
A new call to calloc() via the interposers call ... a nice gotcha to
know about. One would have to be sure they are not interposing the
library itself (e.g. calls inside the library don't get interposed).
Not sure how that is done.
| 4. Symbol versioning: there are two incompatible versions of
| e.g. pthread_cond_init() inside glibc, and if you have 3rd
| party libraries in the picture, it's quite possible for both of
| these versions to be actually used. Correctly interposing both
| symbols requires some tricks, and the use of dlvsym().
|
| Cheers,
These are the kinds of things I was looking for. Thanks for the starter clues.
--
|WARNING: Due to extreme spam, googlegroups.com is blocked. Due to ignorance |
| by the abuse department, bellsouth.net is blocked. If you post to |
| Usenet from these places, find another Usenet provider ASAP. |
| Phil Howard KA9WGN (email for humans: first name in lower case at ipal.net) | |
|
|
| Back to top |
|
| |
Page 2 of 2 Goto page Previous 1, 2
All times are GMT - 5 Hours
The time now is Sat Nov 22, 2008 6:07 am
|
|