[v2] rtld: make ldd show relative addresses and add option for aligned output

Message ID 20210514065652.114062-1-michaeljclark@mac.com
State Rejected
Headers
Series [v2] rtld: make ldd show relative addresses and add option for aligned output |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Michael Clark May 14, 2021, 6:56 a.m. UTC
  This change increases `ldd` readability:

 - modifies trace output to use relative addresses by default.
 - add alternative trace output mode with left-justified addresses.

The relative addresses are composed by subtracting the ELF ehdr address
which makes the output constant under address space layout randomization.
This should be a safe change because the default format is preserved.

The intention is to make `ldd` easier to cross reference with objdump.
Also, log files including `ldd` output will contain less differences.
The vdso is the only address that changes when using relative addresses.

* Justified output *

The new trace format is enabled with `LD_TRACE_ADDR_JUSTIFY=1`, otherwise
the default `ldd` trace format is selected for compatibility.

* Relative addresses *

`ldd` load addresses are displayed relative to the ld.so executable header
address. Relative addresses are enabled by default, given the output mimics
systems without ASLR, thus there should be minimal compatibility issues.
There is also an option to negate addresses as an aid in interpreting them,
seeing library addresses relative to the loader with negative offsets.

The patch adds three new ld.so flags accessible via environment variables:

 - `LD_TRACE_ADDR_JUSTIFY=1` - Show addresses left-justified
 - `LD_TRACE_ADDR_ABSOLUTE=1` - Show absolute addresses (backwards compat)
 - `LD_TRACE_ADDR_NEGATE=1` - Show negated addresses (combination option)

This is a resend. Previous feedback was why do we want to read the load
addresses. Why are they even displayed then? Q.E.D. Relative addresses
make sense, but they are not very readable unless left justified.

Signed-off-by: Michael Clark <michaeljclark@mac.com>
---
 elf/rtld.c | 133 +++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 113 insertions(+), 20 deletions(-)
  

Comments

Michael Clark May 14, 2021, 7:08 a.m. UTC | #1
below is example output. sorry if it does not wrap properly or is munged by the list.

$ LD_TRACE_ADDR_NEGATE=1 LD_TRACE_ADDR_JUSTIFY=1 ldd /usr/bin/Xwayland
     (-0xffffffa05ef70000) linux-vdso.so.1
     (-0x00000000002b9000) libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1
     (-0x00000000003d7000) libgcrypt.so.20 => /usr/lib/x86_64-linux-gnu/libgcrypt.so.20
     (-0x00000000003dd000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2
     (-0x00000000003fa000) libunwind.so.8 => /usr/lib/x86_64-linux-gnu/libunwind.so.8
     (-0x000000000040b000) libwayland-client.so.0 => /usr/lib/x86_64-linux-gnu/libwayland-client.so.0
     (-0x000000000041f000) libdrm.so.2 => /usr/lib/x86_64-linux-gnu/libdrm.so.2
     (-0x00000000004c8000) libpixman-1.so.0 => /usr/lib/x86_64-linux-gnu/libpixman-1.so.0
     (-0x00000000006f6000) libXfont2.so.2 => /usr/lib/x86_64-linux-gnu/libXfont2.so.2
     (-0x00000000006fc000) libXau.so.6 => /usr/lib/x86_64-linux-gnu/libXau.so.6
     (-0x00000000007ab000) libsystemd.so.0 => /lib/x86_64-linux-gnu/libsystemd.so.0
     (-0x00000000009ad000) libxshmfence.so.1 => /usr/lib/x86_64-linux-gnu/libxshmfence.so.1
     (-0x00000000009b5000) libXdmcp.so.6 => /usr/lib/x86_64-linux-gnu/libXdmcp.so.6
     (-0x0000000000aea000) libepoxy.so.0 => /usr/lib/x86_64-linux-gnu/libepoxy.so.0
     (-0x0000000000afc000) libgbm.so.1 => /usr/lib/x86_64-linux-gnu/libgbm.so.1
     (-0x0000000000b84000) libGL.so.1 => /usr/lib/x86_64-linux-gnu/libGL.so.1
     (-0x0000000000bb0000) libaudit.so.1 => /lib/x86_64-linux-gnu/libaudit.so.1
     (-0x0000000000cff000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6
     (-0x0000000000d19000) libbsd.so.0 => /usr/lib/x86_64-linux-gnu/libbsd.so.0
     (-0x0000000000d3e000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0
     (-0x0000000000f30000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
     (-0x0000000000fc0000) libpcre2-8.so.0 => /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0
     (-0x0000000000000000) /lib64/ld-linux-x86-64.so.2
     (-0x0000000000fe3000) libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0
     (-0x000000000100c000) liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5
     (-0x0000000001018000) libffi.so.7 => /usr/lib/x86_64-linux-gnu/libffi.so.7
     (-0x0000000001036000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1
     (-0x0000000001049000) libbz2.so.1.0 => /lib/x86_64-linux-gnu/libbz2.so.1.0
     (-0x0000000001053000) libfontenc.so.1 => /usr/lib/x86_64-linux-gnu/libfontenc.so.1
     (-0x0000000001112000) libfreetype.so.6 => /usr/lib/x86_64-linux-gnu/libfreetype.so.6
     (-0x000000000111d000) librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1
     (-0x000000000113e000) liblz4.so.1 => /usr/lib/x86_64-linux-gnu/liblz4.so.1
     (-0x0000000001156000) libwayland-server.so.0 => /usr/lib/x86_64-linux-gnu/libwayland-server.so.0
     (-0x0000000001184000) libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1
     (-0x000000000123c000) libGLdispatch.so.0 => /usr/lib/x86_64-linux-gnu/libGLdispatch.so.0
     (-0x0000000001270000) libGLX.so.0 => /usr/lib/x86_64-linux-gnu/libGLX.so.0
     (-0x0000000001278000) libcap-ng.so.0 => /lib/x86_64-linux-gnu/libcap-ng.so.0
     (-0x00000000012b2000) libpng16.so.16 => /usr/lib/x86_64-linux-gnu/libpng16.so.16
     (-0x00000000013ef000) libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6
     (-0x0000000001419000) libxcb.so.1 => /usr/lib/x86_64-linux-gnu/libxcb.so.1

On 14/05/21 6:56 pm, Michael Clark wrote:
> This change increases `ldd` readability:
>
>   - modifies trace output to use relative addresses by default.
>   - add alternative trace output mode with left-justified addresses.
>
> The relative addresses are composed by subtracting the ELF ehdr address
> which makes the output constant under address space layout randomization.
> This should be a safe change because the default format is preserved.
>
> The intention is to make `ldd` easier to cross reference with objdump.
> Also, log files including `ldd` output will contain less differences.
> The vdso is the only address that changes when using relative addresses.
>
> * Justified output *
>
> The new trace format is enabled with `LD_TRACE_ADDR_JUSTIFY=1`, otherwise
> the default `ldd` trace format is selected for compatibility.
>
> * Relative addresses *
>
> `ldd` load addresses are displayed relative to the ld.so executable header
> address. Relative addresses are enabled by default, given the output mimics
> systems without ASLR, thus there should be minimal compatibility issues.
> There is also an option to negate addresses as an aid in interpreting them,
> seeing library addresses relative to the loader with negative offsets.
>
> The patch adds three new ld.so flags accessible via environment variables:
>
>   - `LD_TRACE_ADDR_JUSTIFY=1` - Show addresses left-justified
>   - `LD_TRACE_ADDR_ABSOLUTE=1` - Show absolute addresses (backwards compat)
>   - `LD_TRACE_ADDR_NEGATE=1` - Show negated addresses (combination option)
>
> This is a resend. Previous feedback was why do we want to read the load
> addresses. Why are they even displayed then? Q.E.D. Relative addresses
> make sense, but they are not very readable unless left justified.
>
> Signed-off-by: Michael Clark <michaeljclark@mac.com>
> ---
>   elf/rtld.c | 133 +++++++++++++++++++++++++++++++++++++++++++++--------
>   1 file changed, 113 insertions(+), 20 deletions(-)
>
> diff --git a/elf/rtld.c b/elf/rtld.c
> index fbbd60b446da..7fbd283ce14f 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -143,6 +143,24 @@ static void dl_main_state_init (struct dl_main_state *state);
>   extern char **_environ attribute_hidden;
>   static void process_envvars (struct dl_main_state *state);
>   
> +/* Option to display relative load addresses, with display address
> + * having executable base address subtracted, making output constant
> + * in the presence of ASLR, as well as reducing output differences.
> + * `LD_TRACE_ADDR_ABSOLUTE=1` restores prior behavior.  */
> +static int trace_addr_relative = 1;
> +
> +/* Option to negate load addresses, otherwise the default shows
> + * negative relative offsets beacuase ld.so loads libs downwards,
> + * and brk space is just after the executable in memory. The
> + * justified format has sign to show negated address offsets.
> + * `LD_TRACE_ADDR_NEGATE=1` to show signed positive offsets.  */
> +static int trace_addr_negate = 0;
> +
> +/* Option to display left-justified addresses, making the listing
> + * easier to read because addresses are all lined up in one column.
> + * `LD_TRACE_ADDR_JUSTIFY=1` will left-justify addresses.  */
> +static int trace_addr_justify = 0;
> +
>   #ifdef DL_ARGV_NOT_RELRO
>   int _dl_argc attribute_hidden;
>   char **_dl_argv = NULL;
> @@ -1708,6 +1726,8 @@ dl_main (const ElfW(Phdr) *phdr,
>     GL(dl_rtld_map).l_phdr = rtld_phdr;
>     GL(dl_rtld_map).l_phnum = rtld_ehdr->e_phnum;
>   
> +  /* base address (ld.so ehdr) used for relative display addresses */
> +  size_t disp_addr, base_addr = (size_t) rtld_ehdr;
>   
>     /* PT_GNU_RELRO is usually the last phdr.  */
>     size_t cnt = rtld_ehdr->e_phnum;
> @@ -2022,6 +2042,12 @@ dl_main (const ElfW(Phdr) *phdr,
>   	  for (i = 0; i < scope->r_nlist; i++)
>   	    {
>   	      l = scope->r_list [i];
> +
> +	      /* Subtract and negate base from load address if requested  */
> +	      disp_addr = (size_t) l->l_map_start;
> +	      if (trace_addr_relative) disp_addr -= base_addr;
> +	      if (trace_addr_negate) disp_addr = -disp_addr;
> +
>   	      if (l->l_faked)
>   		{
>   		  _dl_printf ("\t%s => not found\n", l->l_libname->name);
> @@ -2029,20 +2055,42 @@ dl_main (const ElfW(Phdr) *phdr,
>   		}
>   	      if (_dl_name_match_p (GLRO(dl_trace_prelink), l))
>   		GLRO(dl_trace_prelink_map) = l;
> -	      _dl_printf ("\t%s => %s (0x%0*Zx, 0x%0*Zx)",
> -			  DSO_FILENAME (l->l_libname->name),
> -			  DSO_FILENAME (l->l_name),
> -			  (int) sizeof l->l_map_start * 2,
> -			  (size_t) l->l_map_start,
> -			  (int) sizeof l->l_addr * 2,
> -			  (size_t) l->l_addr);
> -
> -	      if (l->l_tls_modid)
> -		_dl_printf (" TLS(0x%Zx, 0x%0*Zx)\n", l->l_tls_modid,
> -			    (int) sizeof l->l_tls_offset * 2,
> -			    (size_t) l->l_tls_offset);
> +	      if (trace_addr_justify)
> +	        {
> +		  _dl_printf ("\t(%s0x%0*Zx, 0x%0*Zx)",
> +		          trace_addr_negate ? "-" : "+",
> +			      (int) sizeof l->l_map_start * 2,
> +			      disp_addr,
> +			      (int) sizeof l->l_addr * 2,
> +			      (size_t) l->l_addr);
> +
> +		  if (l->l_tls_modid)
> +		    _dl_printf (" TLS(0x%Zx, 0x%0*Zx)", l->l_tls_modid,
> +				(int) sizeof l->l_tls_offset * 2,
> +				(size_t) l->l_tls_offset);
> +
> +		  _dl_printf (" %s => %s\n",
> +			      DSO_FILENAME (l->l_libname->name),
> +			      DSO_FILENAME (l->l_name));
> +
> +		}
>   	      else
> -		_dl_printf ("\n");
> +		{
> +		  _dl_printf ("\t%s => %s (0x%0*Zx, 0x%0*Zx)",
> +			      DSO_FILENAME (l->l_libname->name),
> +			      DSO_FILENAME (l->l_name),
> +			      (int) sizeof l->l_map_start * 2,
> +			      disp_addr,
> +			      (int) sizeof l->l_addr * 2,
> +			      (size_t) l->l_addr);
> +
> +	          if (l->l_tls_modid)
> +		    _dl_printf (" TLS(0x%Zx, 0x%0*Zx)\n", l->l_tls_modid,
> +			        (int) sizeof l->l_tls_offset * 2,
> +			        (size_t) l->l_tls_offset);
> +	          else
> +		    _dl_printf ("\n");
> +	      }
>   	    }
>   	}
>         else if (GLRO(dl_debug_mask) & DL_DEBUG_UNUSED)
> @@ -2096,17 +2144,41 @@ dl_main (const ElfW(Phdr) *phdr,
>         else
>   	{
>   	  for (l = main_map->l_next; l; l = l->l_next)
> +	  {
> +	    /* Subtract and negate base from load address if requested  */
> +	    disp_addr = (size_t) l->l_map_start;
> +	    if (trace_addr_relative) disp_addr -= base_addr;
> +	    if (trace_addr_negate) disp_addr = -disp_addr;
> +
>   	    if (l->l_faked)
>   	      /* The library was not found.  */
>   	      _dl_printf ("\t%s => not found\n", l->l_libname->name);
> -	    else if (strcmp (l->l_libname->name, l->l_name) == 0)
> -	      _dl_printf ("\t%s (0x%0*Zx)\n", l->l_libname->name,
> -			  (int) sizeof l->l_map_start * 2,
> -			  (size_t) l->l_map_start);
>   	    else
> -	      _dl_printf ("\t%s => %s (0x%0*Zx)\n", l->l_libname->name,
> -			  l->l_name, (int) sizeof l->l_map_start * 2,
> -			  (size_t) l->l_map_start);
> +	      if (trace_addr_justify)
> +		if (strcmp (l->l_libname->name, l->l_name) == 0)
> +		  _dl_printf ("\t(%s0x%0*Zx) %s\n",
> +			      trace_addr_negate ? "-" : "+",
> +			      (int) sizeof l->l_map_start * 2,
> +			      disp_addr,
> +			      l->l_libname->name);
> +		else
> +		  _dl_printf ("\t(%s0x%0*Zx) %s => %s\n",
> +			      trace_addr_negate ? "-" : "+",
> +			      (int) sizeof l->l_map_start * 2,
> +			      disp_addr,
> +			      l->l_libname->name, l->l_name);
> +	      else
> +		if (strcmp (l->l_libname->name, l->l_name) == 0)
> +		  _dl_printf ("\t%s (0x%0*Zx)\n",
> +			      l->l_libname->name,
> +			      (int) sizeof l->l_map_start * 2,
> +			      disp_addr);
> +		else
> +		  _dl_printf ("\t%s => %s (0x%0*Zx)\n",
> +			      l->l_libname->name,
> +			      l->l_name, (int) sizeof l->l_map_start * 2,
> +			      disp_addr);
> +	  }
>   	}
>   
>         if (__glibc_unlikely (state.mode != rtld_mode_trace))
> @@ -2792,6 +2864,27 @@ process_envvars (struct dl_main_state *state)
>   	    }
>   	  break;
>   
> +	case 17:
> +	  /* Addresses can be negated.  */
> +	  if (!__libc_enable_secure
> +	      && memcmp (envline, "TRACE_ADDR_NEGATE", 17) == 0)
> +	      trace_addr_negate = envline[18] != '\0';
> +	  break;
> +
> +	case 18:
> +	  /* Addresses can be left-justified.  */
> +	  if (!__libc_enable_secure
> +	      && memcmp (envline, "TRACE_ADDR_JUSTIFY", 18) == 0)
> +	      trace_addr_justify = envline[19] != '\0';
> +	  break;
> +
> +	case 19:
> +	  /* Absolute addresses can be displayed.  */
> +	  if (!__libc_enable_secure
> +	      && memcmp (envline, "TRACE_ADDR_ABSOLUTE", 19) == 0)
> +	      trace_addr_relative = envline[20] == '\0';
> +	  break;
> +
>   	case 20:
>   	  /* The mode of the dynamic linker can be set.  */
>   	  if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
  
Szabolcs Nagy May 14, 2021, 12:24 p.m. UTC | #2
The 05/14/2021 18:56, Michael Clark via Libc-alpha wrote:
> This change increases `ldd` readability:
> 
>  - modifies trace output to use relative addresses by default.
>  - add alternative trace output mode with left-justified addresses.
> 
> The relative addresses are composed by subtracting the ELF ehdr address
> which makes the output constant under address space layout randomization.
> This should be a safe change because the default format is preserved.

why would the numbers be constant?

the patch seems to subtract the *rtld* ehdr address from
the dso start addresses. aslr may not guarantee fixed
offset between dsos (even if linux does that now).

so if you want stable output just have an option to print 0.

> The intention is to make `ldd` easier to cross reference with objdump.

why is this easier wrt objdump?

> Also, log files including `ldd` output will contain less differences.
> The vdso is the only address that changes when using relative addresses.
...
> +  /* base address (ld.so ehdr) used for relative display addresses */
> +  size_t disp_addr, base_addr = (size_t) rtld_ehdr;
...
> +	      /* Subtract and negate base from load address if requested  */
> +	      disp_addr = (size_t) l->l_map_start;
> +	      if (trace_addr_relative) disp_addr -= base_addr;
> +	      if (trace_addr_negate) disp_addr = -disp_addr;
  
Michael Clark May 14, 2021, 9:57 p.m. UTC | #3
> On 15/05/2021, at 12:24 AM, Szabolcs Nagy <Szabolcs.Nagy@arm.com> wrote:
> 
> The 05/14/2021 18:56, Michael Clark via Libc-alpha wrote:
>> This change increases `ldd` readability:
>> 
>> - modifies trace output to use relative addresses by default.
>> - add alternative trace output mode with left-justified addresses.
>> 
>> The relative addresses are composed by subtracting the ELF ehdr address
>> which makes the output constant under address space layout randomization.
>> This should be a safe change because the default format is preserved.
> 
> why would the numbers be constant?
> 
> the patch seems to subtract the *rtld* ehdr address from
> the dso start addresses. aslr may not guarantee fixed
> offset between dsos (even if linux does that now).

The ehdr address of ld.so is the only address randomised by the kernel. Any other address is mmap’d by rtld. I’m not sure if it uses the default mmap address. I’m guessing it doesn’t. 

> so if you want stable output just have an option to print 0.

Indeed. It kind of shows the problem. There is some vdso randomisation that comes through. The intent of the tool was never to leak thrown away kernel entropy. Originally it showed what the load offsets were then ASLR.

>> The intention is to make `ldd` easier to cross reference with objdump.
> 
> why is this easier wrt objdump?

Trim the numbers or show 0 then.

>> Also, log files including `ldd` output will contain less differences.
>> The vdso is the only address that changes when using relative addresses.
> ...
>> +  /* base address (ld.so ehdr) used for relative display addresses */
>> +  size_t disp_addr, base_addr = (size_t) rtld_ehdr;
> ...
>> +          /* Subtract and negate base from load address if requested  */
>> +          disp_addr = (size_t) l->l_map_start;
>> +          if (trace_addr_relative) disp_addr -= base_addr;
>> +          if (trace_addr_negate) disp_addr = -disp_addr;
  
Michael Clark May 14, 2021, 10:10 p.m. UTC | #4
If I want the actual random numbers I can look in /proc/${pid}/maps

What someone wants for tracing dynamic loading versus what a user wants for ldd output are likely different things. We have to ask ourselves what hiding randomness achieves, when and from whom. ASLR certainly doesn’t make ldd any more useful because it’s entropy is of an exited process.

IDK. I looked at the patch again and the relative addresses were less useful with the misaligned output so I sent it again as a whole. I’m pretty sure about the readability as a user just not whether it’s useful here.

> On 15/05/2021, at 9:57 AM, Michael Clark <michaeljclark@mac.com> wrote:
> 
> 
> 
>>> On 15/05/2021, at 12:24 AM, Szabolcs Nagy <Szabolcs.Nagy@arm.com> wrote:
>>> 
>>> The 05/14/2021 18:56, Michael Clark via Libc-alpha wrote:
>>> This change increases `ldd` readability:
>>> 
>>> - modifies trace output to use relative addresses by default.
>>> - add alternative trace output mode with left-justified addresses.
>>> 
>>> The relative addresses are composed by subtracting the ELF ehdr address
>>> which makes the output constant under address space layout randomization.
>>> This should be a safe change because the default format is preserved.
>> 
>> why would the numbers be constant?
>> 
>> the patch seems to subtract the *rtld* ehdr address from
>> the dso start addresses. aslr may not guarantee fixed
>> offset between dsos (even if linux does that now).
> 
> The ehdr address of ld.so is the only address randomised by the kernel. Any other address is mmap’d by rtld. I’m not sure if it uses the default mmap address. I’m guessing it doesn’t. 
> 
>> so if you want stable output just have an option to print 0.
> 
> Indeed. It kind of shows the problem. There is some vdso randomisation that comes through. The intent of the tool was never to leak thrown away kernel entropy. Originally it showed what the load offsets were then ASLR.
> 
>>> The intention is to make `ldd` easier to cross reference with objdump.
>> 
>> why is this easier wrt objdump?
> 
> Trim the numbers or show 0 then.
> 
>>> Also, log files including `ldd` output will contain less differences.
>>> The vdso is the only address that changes when using relative addresses.
>> ...
>>> +  /* base address (ld.so ehdr) used for relative display addresses */
>>> +  size_t disp_addr, base_addr = (size_t) rtld_ehdr;
>> ...
>>> +          /* Subtract and negate base from load address if requested  */
>>> +          disp_addr = (size_t) l->l_map_start;
>>> +          if (trace_addr_relative) disp_addr -= base_addr;
>>> +          if (trace_addr_negate) disp_addr = -disp_addr;
  
Joseph Myers May 17, 2021, 8 p.m. UTC | #5
On Fri, 14 May 2021, Michael Clark via Libc-alpha wrote:

> The patch adds three new ld.so flags accessible via environment variables:
> 
>  - `LD_TRACE_ADDR_JUSTIFY=1` - Show addresses left-justified
>  - `LD_TRACE_ADDR_ABSOLUTE=1` - Show absolute addresses (backwards compat)
>  - `LD_TRACE_ADDR_NEGATE=1` - Show negated addresses (combination option)

As a new user-visible feature, the patch needs to add information about it 
to the NEWS file ("Major new features" under the heading for the next 
release) rather than only having it in the commit message.  (We don't yet 
have documentation in the glibc manual for the dynamic linker and ldd - 
see Ben Woodard's patches from a few years ago - but if we did, the patch 
would need to update that documentation to cover the new feature as well.)
  
Florian Weimer May 18, 2021, 5:07 a.m. UTC | #6
* Szabolcs Nagy via Libc-alpha:

> The 05/14/2021 18:56, Michael Clark via Libc-alpha wrote:
>> This change increases `ldd` readability:
>> 
>>  - modifies trace output to use relative addresses by default.
>>  - add alternative trace output mode with left-justified addresses.
>> 
>> The relative addresses are composed by subtracting the ELF ehdr address
>> which makes the output constant under address space layout randomization.
>> This should be a safe change because the default format is preserved.
>
> why would the numbers be constant?
>
> the patch seems to subtract the *rtld* ehdr address from
> the dso start addresses. aslr may not guarantee fixed
> offset between dsos (even if linux does that now).
>
> so if you want stable output just have an option to print 0.

Slightly more compatible would be to use a fake starting address and
increment it by the size of the previous mapping for the next one.  This
would be consistent from run to run.

Thanks,
Florian
  
Adhemerval Zanella May 18, 2021, 1:10 p.m. UTC | #7
On 18/05/2021 02:07, Florian Weimer via Libc-alpha wrote:
> * Szabolcs Nagy via Libc-alpha:
> 
>> The 05/14/2021 18:56, Michael Clark via Libc-alpha wrote:
>>> This change increases `ldd` readability:
>>>
>>>  - modifies trace output to use relative addresses by default.
>>>  - add alternative trace output mode with left-justified addresses.
>>>
>>> The relative addresses are composed by subtracting the ELF ehdr address
>>> which makes the output constant under address space layout randomization.
>>> This should be a safe change because the default format is preserved.
>>
>> why would the numbers be constant?
>>
>> the patch seems to subtract the *rtld* ehdr address from
>> the dso start addresses. aslr may not guarantee fixed
>> offset between dsos (even if linux does that now).
>>
>> so if you want stable output just have an option to print 0.
> 
> Slightly more compatible would be to use a fake starting address and
> increment it by the size of the previous mapping for the next one.  This
> would be consistent from run to run.

On the previous version my take was it would be better to be
implemented with a script over rtld output, I am not sure adding
this extra complexity on the loader is the best approach.
  
Florian Weimer May 18, 2021, 2:01 p.m. UTC | #8
* Adhemerval Zanella via Libc-alpha:

> On 18/05/2021 02:07, Florian Weimer via Libc-alpha wrote:
>> * Szabolcs Nagy via Libc-alpha:
>> 
>>> The 05/14/2021 18:56, Michael Clark via Libc-alpha wrote:
>>>> This change increases `ldd` readability:
>>>>
>>>>  - modifies trace output to use relative addresses by default.
>>>>  - add alternative trace output mode with left-justified addresses.
>>>>
>>>> The relative addresses are composed by subtracting the ELF ehdr address
>>>> which makes the output constant under address space layout randomization.
>>>> This should be a safe change because the default format is preserved.
>>>
>>> why would the numbers be constant?
>>>
>>> the patch seems to subtract the *rtld* ehdr address from
>>> the dso start addresses. aslr may not guarantee fixed
>>> offset between dsos (even if linux does that now).
>>>
>>> so if you want stable output just have an option to print 0.
>> 
>> Slightly more compatible would be to use a fake starting address and
>> increment it by the size of the previous mapping for the next one.  This
>> would be consistent from run to run.
>
> On the previous version my take was it would be better to be
> implemented with a script over rtld output, I am not sure adding
> this extra complexity on the loader is the best approach.

The size information is not available.  But one could just pretend each
object is 64 KiB large I guess.

Thanks,
Florian
  

Patch

diff --git a/elf/rtld.c b/elf/rtld.c
index fbbd60b446da..7fbd283ce14f 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -143,6 +143,24 @@  static void dl_main_state_init (struct dl_main_state *state);
 extern char **_environ attribute_hidden;
 static void process_envvars (struct dl_main_state *state);
 
+/* Option to display relative load addresses, with display address
+ * having executable base address subtracted, making output constant
+ * in the presence of ASLR, as well as reducing output differences.
+ * `LD_TRACE_ADDR_ABSOLUTE=1` restores prior behavior.  */
+static int trace_addr_relative = 1;
+
+/* Option to negate load addresses, otherwise the default shows
+ * negative relative offsets beacuase ld.so loads libs downwards,
+ * and brk space is just after the executable in memory. The
+ * justified format has sign to show negated address offsets.
+ * `LD_TRACE_ADDR_NEGATE=1` to show signed positive offsets.  */
+static int trace_addr_negate = 0;
+
+/* Option to display left-justified addresses, making the listing
+ * easier to read because addresses are all lined up in one column.
+ * `LD_TRACE_ADDR_JUSTIFY=1` will left-justify addresses.  */
+static int trace_addr_justify = 0;
+
 #ifdef DL_ARGV_NOT_RELRO
 int _dl_argc attribute_hidden;
 char **_dl_argv = NULL;
@@ -1708,6 +1726,8 @@  dl_main (const ElfW(Phdr) *phdr,
   GL(dl_rtld_map).l_phdr = rtld_phdr;
   GL(dl_rtld_map).l_phnum = rtld_ehdr->e_phnum;
 
+  /* base address (ld.so ehdr) used for relative display addresses */
+  size_t disp_addr, base_addr = (size_t) rtld_ehdr;
 
   /* PT_GNU_RELRO is usually the last phdr.  */
   size_t cnt = rtld_ehdr->e_phnum;
@@ -2022,6 +2042,12 @@  dl_main (const ElfW(Phdr) *phdr,
 	  for (i = 0; i < scope->r_nlist; i++)
 	    {
 	      l = scope->r_list [i];
+
+	      /* Subtract and negate base from load address if requested  */
+	      disp_addr = (size_t) l->l_map_start;
+	      if (trace_addr_relative) disp_addr -= base_addr;
+	      if (trace_addr_negate) disp_addr = -disp_addr;
+
 	      if (l->l_faked)
 		{
 		  _dl_printf ("\t%s => not found\n", l->l_libname->name);
@@ -2029,20 +2055,42 @@  dl_main (const ElfW(Phdr) *phdr,
 		}
 	      if (_dl_name_match_p (GLRO(dl_trace_prelink), l))
 		GLRO(dl_trace_prelink_map) = l;
-	      _dl_printf ("\t%s => %s (0x%0*Zx, 0x%0*Zx)",
-			  DSO_FILENAME (l->l_libname->name),
-			  DSO_FILENAME (l->l_name),
-			  (int) sizeof l->l_map_start * 2,
-			  (size_t) l->l_map_start,
-			  (int) sizeof l->l_addr * 2,
-			  (size_t) l->l_addr);
-
-	      if (l->l_tls_modid)
-		_dl_printf (" TLS(0x%Zx, 0x%0*Zx)\n", l->l_tls_modid,
-			    (int) sizeof l->l_tls_offset * 2,
-			    (size_t) l->l_tls_offset);
+	      if (trace_addr_justify)
+	        {
+		  _dl_printf ("\t(%s0x%0*Zx, 0x%0*Zx)",
+		          trace_addr_negate ? "-" : "+",
+			      (int) sizeof l->l_map_start * 2,
+			      disp_addr,
+			      (int) sizeof l->l_addr * 2,
+			      (size_t) l->l_addr);
+
+		  if (l->l_tls_modid)
+		    _dl_printf (" TLS(0x%Zx, 0x%0*Zx)", l->l_tls_modid,
+				(int) sizeof l->l_tls_offset * 2,
+				(size_t) l->l_tls_offset);
+
+		  _dl_printf (" %s => %s\n",
+			      DSO_FILENAME (l->l_libname->name),
+			      DSO_FILENAME (l->l_name));
+
+		}
 	      else
-		_dl_printf ("\n");
+		{
+		  _dl_printf ("\t%s => %s (0x%0*Zx, 0x%0*Zx)",
+			      DSO_FILENAME (l->l_libname->name),
+			      DSO_FILENAME (l->l_name),
+			      (int) sizeof l->l_map_start * 2,
+			      disp_addr,
+			      (int) sizeof l->l_addr * 2,
+			      (size_t) l->l_addr);
+
+	          if (l->l_tls_modid)
+		    _dl_printf (" TLS(0x%Zx, 0x%0*Zx)\n", l->l_tls_modid,
+			        (int) sizeof l->l_tls_offset * 2,
+			        (size_t) l->l_tls_offset);
+	          else
+		    _dl_printf ("\n");
+	      }
 	    }
 	}
       else if (GLRO(dl_debug_mask) & DL_DEBUG_UNUSED)
@@ -2096,17 +2144,41 @@  dl_main (const ElfW(Phdr) *phdr,
       else
 	{
 	  for (l = main_map->l_next; l; l = l->l_next)
+	  {
+	    /* Subtract and negate base from load address if requested  */
+	    disp_addr = (size_t) l->l_map_start;
+	    if (trace_addr_relative) disp_addr -= base_addr;
+	    if (trace_addr_negate) disp_addr = -disp_addr;
+
 	    if (l->l_faked)
 	      /* The library was not found.  */
 	      _dl_printf ("\t%s => not found\n", l->l_libname->name);
-	    else if (strcmp (l->l_libname->name, l->l_name) == 0)
-	      _dl_printf ("\t%s (0x%0*Zx)\n", l->l_libname->name,
-			  (int) sizeof l->l_map_start * 2,
-			  (size_t) l->l_map_start);
 	    else
-	      _dl_printf ("\t%s => %s (0x%0*Zx)\n", l->l_libname->name,
-			  l->l_name, (int) sizeof l->l_map_start * 2,
-			  (size_t) l->l_map_start);
+	      if (trace_addr_justify)
+		if (strcmp (l->l_libname->name, l->l_name) == 0)
+		  _dl_printf ("\t(%s0x%0*Zx) %s\n",
+			      trace_addr_negate ? "-" : "+",
+			      (int) sizeof l->l_map_start * 2,
+			      disp_addr,
+			      l->l_libname->name);
+		else
+		  _dl_printf ("\t(%s0x%0*Zx) %s => %s\n",
+			      trace_addr_negate ? "-" : "+",
+			      (int) sizeof l->l_map_start * 2,
+			      disp_addr,
+			      l->l_libname->name, l->l_name);
+	      else
+		if (strcmp (l->l_libname->name, l->l_name) == 0)
+		  _dl_printf ("\t%s (0x%0*Zx)\n",
+			      l->l_libname->name,
+			      (int) sizeof l->l_map_start * 2,
+			      disp_addr);
+		else
+		  _dl_printf ("\t%s => %s (0x%0*Zx)\n",
+			      l->l_libname->name,
+			      l->l_name, (int) sizeof l->l_map_start * 2,
+			      disp_addr);
+	  }
 	}
 
       if (__glibc_unlikely (state.mode != rtld_mode_trace))
@@ -2792,6 +2864,27 @@  process_envvars (struct dl_main_state *state)
 	    }
 	  break;
 
+	case 17:
+	  /* Addresses can be negated.  */
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "TRACE_ADDR_NEGATE", 17) == 0)
+	      trace_addr_negate = envline[18] != '\0';
+	  break;
+
+	case 18:
+	  /* Addresses can be left-justified.  */
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "TRACE_ADDR_JUSTIFY", 18) == 0)
+	      trace_addr_justify = envline[19] != '\0';
+	  break;
+
+	case 19:
+	  /* Absolute addresses can be displayed.  */
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "TRACE_ADDR_ABSOLUTE", 19) == 0)
+	      trace_addr_relative = envline[20] == '\0';
+	  break;
+
 	case 20:
 	  /* The mode of the dynamic linker can be set.  */
 	  if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)