I''ve been trying to get access to a userland string that''s behind a second level pointer using DTrace, but I can''t seem to get it to work. I started with the example on the Team DTrace Tips and Tricks slides: trace(copyinstr(*(uintptr_t *)copyin(arg0, curpsinfo->pr_dmodel == PR_MODEL_ILP32 ? 4 : 8))); And when I couldn''t get it to work, I started breaking it down into smaller pieces to see what was going on at each step. Here''s the "C" and "D" code: --- pt.c --- static int sz = sizeof(char *); main() { char *s = "abcdefghijk"; char **p = &s; printf ("Size of pointer = %d\n", sz); sub1(s); sub2(p); exit(0); } sub1 (char *arg) { if (sz == 4) printf ("First 32 bit pointer = %x\n", arg); else printf ("First 64 bit pointer = %llx\n", arg); puts (arg); return; } sub2 (char **arg) { if (sz == 4) printf ("Second 32 bit pointer = %x\n", arg); else printf ("Second 64 bit pointer = %llx\n", arg); puts (*arg); return; } --- pt.d --- pid$target::sub1:entry { trace (copyinstr(arg0)); } pid$target::sub2:entry { sz = curpsinfo->pr_dmodel == PR_MODEL_ILP32 ? 4 : 8; printf ("Userland pointer size = %d\n", sz); } pid$target::sub2:entry { pcp = (uint32_t *) copyin (arg0, sz); /* pcp is ptr to copy of char * */ printf ("D pointer pcp = %a\n", pcp); } pid$target::sub2:entry { cp = *pcp; /* cp is copy of char * */ printf ("%d byte userland pointer = %a\n", sizeof (cp), cp); } pid$target::sub2:entry { str = copyinstr(cp); /* str is the actual string */ trace (str); } When I enter the command : "dtrace -s pt.d -c ./pt", I get: dtrace: script ''pt2.d'' matched 5 probes Size of pointer = 4 First 32 bit pointer = 108a8 abcdefghijk Second 32 bit pointer = ffbffdec abcdefghijk CPU ID FUNCTION:NAME 0 38670 sub1:entry abcdefghijk 0 38671 sub2:entry Userland pointer size = 4 0 38671 sub2:entry D pointer pcp = 0x300adeda120 0 38671 sub2:entry 4 byte userland pointer = 0x4 dtrace: error on enabled probe ID 5 (ID 38671: pid10122:pt:sub2:entry): invalid address (0x0) in action #1 at DIF offset 16 dtrace: pid 10122 has exited Obviously, what I''m copying is not the right char pointer. What am I missing? One side note: the example on the slide used uintptr_t as the type for the userland pointer. Using that instead of uint32_t and uint64_t, didn''t work for me either, but I changed it because, even though my application seems to be 32 bit, sizeof(uintptr_t) kept returning 8 bytes. Also, I''m using gcc, if that matters. Thanks, Chip
On Tue, Aug 08, 2006 at 03:06:45PM -0500, Chip Bennett wrote:> I''ve been trying to get access to a userland string that''s behind a > second level pointer using DTrace, but I can''t seem to get it to work. > > I started with the example on the Team DTrace Tips and Tricks slides: > > trace(copyinstr(*(uintptr_t *)copyin(arg0, curpsinfo->pr_dmodel == > PR_MODEL_ILP32 ? 4 : 8))); > > And when I couldn''t get it to work, I started breaking it down into > smaller pieces to see what was going on at each step. Here''s the "C" > and "D" code: > > --- pt.c ---...> --- pt.d --- > > pid$target::sub1:entry > { > trace (copyinstr(arg0)); > } > pid$target::sub2:entry > { > sz = curpsinfo->pr_dmodel == PR_MODEL_ILP32 ? 4 : 8; > printf ("Userland pointer size = %d\n", sz); > } > pid$target::sub2:entry > { > pcp = (uint32_t *) copyin (arg0, sz); /* pcp is ptr to copy of > char * */ > printf ("D pointer pcp = %a\n", pcp); > }**** after the enabling ends, pcp points to garbage (it''s scratch space) ***> pid$target::sub2:entry > { > cp = *pcp; /* cp is copy of char * */ > printf ("%d byte userland pointer = %a\n", sizeof (cp), cp); > } > pid$target::sub2:entry > { > str = copyinstr(cp); /* str is the actual string */ > trace (str); > }The problem is that the storage used by copyin() is released at the end of the *enabling* (*NOT* the end of the probe firing). You need to copy the values you need out of the copyin()ed result before the ''}'' for your clause. By re-writing your example to: --- cut here --- pid$target::sub1:entry { trace(copyinstr(arg0)); } pid$target::sub2:entry { sz = curpsinfo->pr_dmodel == PR_MODEL_ILP32 ? 4 : 8; printf("Userland pointer size = %d\n", sz); } pid$target::sub2:entry { cp = *(uint32_t *)copyin(arg0, sz); printf("%d byte userland pointer = %a\n", sizeof (cp), cp); } pid$target::sub2:entry { str = copyinstr(cp); /* str is the actual string */ trace(str); } --- cut here --- I get: % dtrace -s pt.d -c ./pt dtrace: script ''tmpc.d'' matched 4 probes Size of pointer = 4 First 32 bit pointer = 20f64 abcdefghijk Second 32 bit pointer = ffbff7b8 abcdefghijk dtrace: pid 186527 has exited CPU ID FUNCTION:NAME 1 46206 sub1:entry abcdefghijk 1 46207 sub2:entry Userland pointer size = 4 1 46207 sub2:entry 4 byte userland pointer = 0x20f64 1 46207 sub2:entry abcdefghijk % Which looks correct.> One side note: the example on the slide used uintptr_t as the type for > the userland pointer. Using that instead of uint32_t and uint64_t, > didn''t work for me either, but I changed it because, even though my > application seems to be 32 bit, sizeof(uintptr_t) kept returning 8 bytes.Types are always evaluated as kernel types.> Also, I''m using gcc, if that matters.It shouldn''t matter. Cheers, - jonathan -- Jonathan Adams, Solaris Kernel Development
Nicolas Williams
2006-Aug-08 21:04 UTC
[dtrace-discuss] Handling userland char ** pointers
On Tue, Aug 08, 2006 at 01:30:41PM -0700, Jonathan Adams wrote:> The problem is that the storage used by copyin() is released at the end of > the *enabling* (*NOT* the end of the probe firing). You need to copy the > values you need out of the copyin()ed result before the ''}'' for your clause.Or he could use a single probe to do the trace(copyinstr(copyin())): pid$target::sub2:entry { trace(copyinstr(*(uintptr_t *)copyin(arg0, (curpsinfo->pr_dmodel == PR_MODEL_ILP32) ? 4 : 8))); } Worked for me on snv_40. I was going to follow up to say this, but something nags me: the dereference of the return value of copying after casting it to uintptr_t. This part of the expression is the same whether the userland program is a 32-bit or 64-bit program, and I''m surprised it works... That is, in this case copyin() returns a pointer to a 32-bit or 64-bit quantity, but the rest of the script treats that pointer the same way and it works. Why?> Types are always evaluated as kernel types.Sure, but how did the cast to uintptr_t * know how many bytes were returned by copyin()? I would think something like this would be correct, that here DTrace would know the size of the data copied in because of the first cast to uin32_t * or uint64_t *: pid$target::sub2:entry /curpsinfo->pr_dmodel == PR_MODEL_ILP32/ { this->p32 = (uint32_t *)copyin(arg0, 4); trace(copyinstr(*(uintptr_t *)this->p32)); } pid$target::sub2:entry /curpsinfo->pr_dmodel == PR_MODEL_LP64/ { this->p64 = (uint32_t *)copyin(arg0, 8); trace(copyinstr(*(uintptr_t *)this->p64)); } Nico --
Greetings Jonathan, I knew that, but I broke up the probe clause so I could see the intermediate trace data. My initial "one big" probe clause example didn''t work either, but for a different reason: At first, I was using the (uintptr_t *) cast from the Tips and Tricks example. Some time after breaking it up, I changed it to (uint32_t *), and didn''t realize that that had fixed my first problem and I had actually traded one problem for another one. Thanks! Chip Jonathan Adams wrote:> On Tue, Aug 08, 2006 at 03:06:45PM -0500, Chip Bennett wrote: > >> I''ve been trying to get access to a userland string that''s behind a >> second level pointer using DTrace, but I can''t seem to get it to work. >> >> I started with the example on the Team DTrace Tips and Tricks slides: >> >> trace(copyinstr(*(uintptr_t *)copyin(arg0, curpsinfo->pr_dmodel == >> PR_MODEL_ILP32 ? 4 : 8))); >> >> And when I couldn''t get it to work, I started breaking it down into >> smaller pieces to see what was going on at each step. Here''s the "C" >> and "D" code: >> >> --- pt.c --- >> > ... > >> --- pt.d --- >> >> pid$target::sub1:entry >> { >> trace (copyinstr(arg0)); >> } >> pid$target::sub2:entry >> { >> sz = curpsinfo->pr_dmodel == PR_MODEL_ILP32 ? 4 : 8; >> printf ("Userland pointer size = %d\n", sz); >> } >> pid$target::sub2:entry >> { >> pcp = (uint32_t *) copyin (arg0, sz); /* pcp is ptr to copy of >> char * */ >> printf ("D pointer pcp = %a\n", pcp); >> } >> > **** after the enabling ends, pcp points to garbage (it''s scratch space) *** > >> pid$target::sub2:entry >> { >> cp = *pcp; /* cp is copy of char * */ >> printf ("%d byte userland pointer = %a\n", sizeof (cp), cp); >> } >> pid$target::sub2:entry >> { >> str = copyinstr(cp); /* str is the actual string */ >> trace (str); >> } >> > > The problem is that the storage used by copyin() is released at the end of > the *enabling* (*NOT* the end of the probe firing). You need to copy the > values you need out of the copyin()ed result before the ''}'' for your clause. > > By re-writing your example to: > > --- cut here --- > pid$target::sub1:entry > { > trace(copyinstr(arg0)); > } > pid$target::sub2:entry > { > sz = curpsinfo->pr_dmodel == PR_MODEL_ILP32 ? 4 : 8; > printf("Userland pointer size = %d\n", sz); > } > pid$target::sub2:entry > { > cp = *(uint32_t *)copyin(arg0, sz); > printf("%d byte userland pointer = %a\n", sizeof (cp), cp); > } > pid$target::sub2:entry > { > str = copyinstr(cp); /* str is the actual string */ > trace(str); > } > --- cut here --- > > I get: > > > % dtrace -s pt.d -c ./pt > dtrace: script ''tmpc.d'' matched 4 probes > Size of pointer = 4 > First 32 bit pointer = 20f64 > abcdefghijk > Second 32 bit pointer = ffbff7b8 > abcdefghijk > dtrace: pid 186527 has exited > CPU ID FUNCTION:NAME > 1 46206 sub1:entry abcdefghijk > 1 46207 sub2:entry Userland pointer size = 4 > > 1 46207 sub2:entry 4 byte userland pointer = 0x20f64 > > 1 46207 sub2:entry abcdefghijk > > % > > Which looks correct. > > >> One side note: the example on the slide used uintptr_t as the type for >> the userland pointer. Using that instead of uint32_t and uint64_t, >> didn''t work for me either, but I changed it because, even though my >> application seems to be 32 bit, sizeof(uintptr_t) kept returning 8 bytes. >> > > Types are always evaluated as kernel types. > > >> Also, I''m using gcc, if that matters. >> > > It shouldn''t matter. > > Cheers, > - jonathan > >
On Tue, Aug 08, 2006 at 04:04:02PM -0500, Nicolas Williams wrote:> On Tue, Aug 08, 2006 at 01:30:41PM -0700, Jonathan Adams wrote: > > The problem is that the storage used by copyin() is released at the end of > > the *enabling* (*NOT* the end of the probe firing). You need to copy the > > values you need out of the copyin()ed result before the ''}'' for your clause. > > Or he could use a single probe to do the trace(copyinstr(copyin())): > > pid$target::sub2:entry > { > trace(copyinstr(*(uintptr_t *)copyin(arg0, > (curpsinfo->pr_dmodel == PR_MODEL_ILP32) ? 4 : 8))); > } > > Worked for me on snv_40. > > I was going to follow up to say this, but something nags me: the > dereference of the return value of copying after casting it to > uintptr_t. This part of the expression is the same whether the userland > program is a 32-bit or 64-bit program, and I''m surprised it works...Uh, this is likely to work on little-endian machines, but not on big-endian machines. And only on little-endian machines if the space after the buffer is all zeros. uintptr_t on a 64-bit kernel is always 64-bits. I''d do: pid$target::sub2:entry /curpsinfo->pr_dmodel == PR_MODEL_ILP32/ { trace(copyinstr(*(uint32_t *)copyin(arg0, 4))); } pid$target::sub2:entry /curpsinfo->pr_dmodel == PR_MODEL_LP64/ { trace(copyinstr(*(uint64_t *)copyin(arg0, 8))); }> That is, in this case copyin() returns a pointer to a 32-bit or 64-bit > quantity, but the rest of the script treats that pointer the same way > and it works. Why?Because it''s just a pointer, and you end up reading past the end of the allocated space.> > Types are always evaluated as kernel types. > > Sure, but how did the cast to uintptr_t * know how many bytes were > returned by copyin()? > > I would think something like this would be correct, that here DTrace > would know the size of the data copied in because of the first cast to > uin32_t * or uint64_t *: > > pid$target::sub2:entry > /curpsinfo->pr_dmodel == PR_MODEL_ILP32/ > { > this->p32 = (uint32_t *)copyin(arg0, 4); > trace(copyinstr(*(uintptr_t *)this->p32));^^^^^^^^^^^ wrong. uint32_t.> }> > pid$target::sub2:entry > /curpsinfo->pr_dmodel == PR_MODEL_LP64/ > { > this->p64 = (uint32_t *)copyin(arg0, 8);^^^^^^^^ wrong. uint64_t or uintptr_t> trace(copyinstr(*(uintptr_t *)this->p64)); > }D pointers are a lot like C pointers; the compiler and runtime assumes that you know what you''re doing, and undefined behavior is just a small mistake away. Cheers, - jonathan -- Jonathan Adams, Solaris Kernel Development
Nicolas Williams
2006-Aug-08 21:16 UTC
[dtrace-discuss] Handling userland char ** pointers
On Tue, Aug 08, 2006 at 02:09:48PM -0700, Jonathan Adams wrote:> On Tue, Aug 08, 2006 at 04:04:02PM -0500, Nicolas Williams wrote: > > I was going to follow up to say this, but something nags me: the > > dereference of the return value of copying after casting it to > > uintptr_t. This part of the expression is the same whether the userland > > program is a 32-bit or 64-bit program, and I''m surprised it works... > > Uh, this is likely to work on little-endian machines, but not on big-endian > machines. And only on little-endian machines if the space after the buffer > is all zeros. uintptr_t on a 64-bit kernel is always 64-bits.I suspected something was wrong.> I''d do: > > pid$target::sub2:entry > /curpsinfo->pr_dmodel == PR_MODEL_ILP32/ > { > trace(copyinstr(*(uint32_t *)copyin(arg0, 4))); > } > > pid$target::sub2:entry > /curpsinfo->pr_dmodel == PR_MODEL_LP64/ > { > trace(copyinstr(*(uint64_t *)copyin(arg0, 8))); > }Yup. I meant something like that but typed it in before trying it. The prototypes for copyin*() in the docs say to use uintptr_t, and I suspected that was a bit of a magic type. Nico --
Nicolas Williams wrote:> On Tue, Aug 08, 2006 at 02:09:48PM -0700, Jonathan Adams wrote: > >> On Tue, Aug 08, 2006 at 04:04:02PM -0500, Nicolas Williams wrote: >> >>> I was going to follow up to say this, but something nags me: the >>> dereference of the return value of copying after casting it to >>> uintptr_t. This part of the expression is the same whether the userland >>> program is a 32-bit or 64-bit program, and I''m surprised it works... >>> >> Uh, this is likely to work on little-endian machines, but not on big-endian >> machines. And only on little-endian machines if the space after the buffer >> is all zeros. uintptr_t on a 64-bit kernel is always 64-bits. >> > > I suspected something was wrong. > > >> I''d do: >> >> pid$target::sub2:entry >> /curpsinfo->pr_dmodel == PR_MODEL_ILP32/ >> { >> trace(copyinstr(*(uint32_t *)copyin(arg0, 4))); >> } >> >> pid$target::sub2:entry >> /curpsinfo->pr_dmodel == PR_MODEL_LP64/ >> { >> trace(copyinstr(*(uint64_t *)copyin(arg0, 8))); >> } >> > > Yup. I meant something like that but typed it in before trying it. > > The prototypes for copyin*() in the docs say to use uintptr_t, and I > suspected that was a bit of a magic type. > > Nico >It appears that D, like C does with integer arguments, is automatically extending the 4 byte pointer to 8 bytes when it passes one as an argument. But this means it has to know whether "arg0" is a 4 byte pointer or an 8 byte pointer. I think it would have been simpler if the writers had allowed "D" to use that knowledge to extend return pointers automatically also. But maybe there''s a good reason not to do that. Chip
On Tue, Aug 08, 2006 at 04:40:42PM -0500, Chip Bennett wrote:> Nicolas Williams wrote: > >On Tue, Aug 08, 2006 at 02:09:48PM -0700, Jonathan Adams wrote: > > > >>On Tue, Aug 08, 2006 at 04:04:02PM -0500, Nicolas Williams wrote: > >> > >>>I was going to follow up to say this, but something nags me: the > >>>dereference of the return value of copying after casting it to > >>>uintptr_t. This part of the expression is the same whether the userland > >>>program is a 32-bit or 64-bit program, and I''m surprised it works... > >>> > >>Uh, this is likely to work on little-endian machines, but not on > >>big-endian > >>machines. And only on little-endian machines if the space after the > >>buffer > >>is all zeros. uintptr_t on a 64-bit kernel is always 64-bits. > >> > > > >I suspected something was wrong. > > > > > >>I''d do: > >> > >>pid$target::sub2:entry > >>/curpsinfo->pr_dmodel == PR_MODEL_ILP32/ > >>{ > >> trace(copyinstr(*(uint32_t *)copyin(arg0, 4))); > >>} > >> > >>pid$target::sub2:entry > >>/curpsinfo->pr_dmodel == PR_MODEL_LP64/ > >>{ > >> trace(copyinstr(*(uint64_t *)copyin(arg0, 8))); > >>} > >> > > > >Yup. I meant something like that but typed it in before trying it. > > > >The prototypes for copyin*() in the docs say to use uintptr_t, and I > >suspected that was a bit of a magic type. > > > >Nico > > > It appears that D, like C does with integer arguments, is automatically > extending the 4 byte pointer to 8 bytes when it passes one as an > argument. But this means it has to know whether "arg0" is a 4 byte > pointer or an 8 byte pointer. I think it would have been simpler if the > writers had allowed "D" to use that knowledge to extend return pointers > automatically also. But maybe there''s a good reason not to do that.The problem is that the expansion is being done in different places. There are some plans (nothing implemented yet, as far as I know) to have D more directly allow manipulation of user data, which would probably help here. Cheers, - jonathan -- Jonathan Adams, Solaris Kernel Development
Nicolas Williams
2006-Aug-08 22:08 UTC
[dtrace-discuss] Handling userland char ** pointers
On Tue, Aug 08, 2006 at 02:55:26PM -0700, Jonathan Adams wrote:> The problem is that the expansion is being done in different places. There > are some plans (nothing implemented yet, as far as I know) to have D more > directly allow manipulation of user data, which would probably help here.It would be very nice if DTrace could be aware of user-land CTF data and able to chase pointers in user-land, though it might complicate the syntax. Perhaps a keyword could be added to distinguish user-land and kernel-land types? But, then header inclusion would be complicated (or maybe not). Nico --
Nicolas Williams wrote:> On Tue, Aug 08, 2006 at 02:55:26PM -0700, Jonathan Adams wrote: > >> The problem is that the expansion is being done in different places. There >> are some plans (nothing implemented yet, as far as I know) to have D more >> directly allow manipulation of user data, which would probably help here. >> > > It would be very nice if DTrace could be aware of user-land CTF data and > able to chase pointers in user-land, though it might complicate the > syntax. > > Perhaps a keyword could be added to distinguish user-land and > kernel-land types? But, then header inclusion would be complicated (or > maybe not). > > Nico >#pragma D option simple-pointers :-)