ld/pdb: Terminate the string list in S_ENVBLOCK
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_binutils_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_binutils_check--master-arm |
success
|
Test passed
|
Commit Message
The list of strings in an S_ENVBLOCK symbol is terminated by an empty
string, which we never wrote. When the strings happened to end on a
four byte boundary, there was no alignment padding to stand in for the
terminator, and readers then ran off the end of the record; llvm-pdbutil
reported "Stream Error: The stream is too short to perform the requested
operation".
ld/
* pdb.c (create_linker_symbols): Terminate the list of strings in
the S_ENVBLOCK symbol.
---
ld/pdb.c | 3 +++
1 file changed, 3 insertions(+)
Comments
On 30.09.2026 21:37, Oleg Tolmatcev wrote:
> The list of strings in an S_ENVBLOCK symbol is terminated by an empty
> string, which we never wrote. When the strings happened to end on a
> four byte boundary, there was no alignment padding to stand in for the
> terminator, and readers then ran off the end of the record; llvm-pdbutil
> reported "Stream Error: The stream is too short to perform the requested
> operation".
>
> ld/
> * pdb.c (create_linker_symbols): Terminate the list of strings in
> the S_ENVBLOCK symbol.
> ---
> ld/pdb.c | 3 +++
> 1 file changed, 3 insertions(+)
Again - looks plausible, but without a pointer to where in the spec this
is mandated, I can only again ask: Mark, could you please take a look?
Jan
> diff --git a/ld/pdb.c b/ld/pdb.c
> index 3885340d567..e0bdc1122fd 100644
> --- a/ld/pdb.c
> +++ b/ld/pdb.c
> @@ -3811,6 +3811,7 @@ create_linker_symbols (bfd *abfd, uint8_t **syms, uint32_t *sym_byte_size,
> env_size += strlen (exeval) + 1;
> env_size += sizeof (pdb);
> env_size += strlen (pdbval) + 1;
> + env_size++; /* Terminating empty string. */
>
> if (env_size % 4)
> padding2 = 4 - (env_size % 4);
> @@ -3883,6 +3884,8 @@ create_linker_symbols (bfd *abfd, uint8_t **syms, uint32_t *sym_byte_size,
> memcpy (ptr, pdbval, strlen (pdbval) + 1);
> ptr += strlen (pdbval) + 1;
>
> + *ptr++ = 0;
> +
> /* Microsoft's LINK also includes "cmd", the command-line options passed
> to the linker, but unfortunately we don't have access to argc and argv
> at this stage. */
пт, 2 окт. 2026 г. в 12:23, Jan Beulich <jbeulich@suse.com>:
>
> On 30.09.2026 21:37, Oleg Tolmatcev wrote:
> > The list of strings in an S_ENVBLOCK symbol is terminated by an empty
> > string, which we never wrote. When the strings happened to end on a
> > four byte boundary, there was no alignment padding to stand in for the
> > terminator, and readers then ran off the end of the record; llvm-pdbutil
> > reported "Stream Error: The stream is too short to perform the requested
> > operation".
> >
> > ld/
> > * pdb.c (create_linker_symbols): Terminate the list of strings in
> > the S_ENVBLOCK symbol.
> > ---
> > ld/pdb.c | 3 +++
> > 1 file changed, 3 insertions(+)
>
> Again - looks plausible, but without a pointer to where in the spec this
> is mandated, I can only again ask: Mark, could you please take a look?
>
> Jan
>
I will try to answer this one too. LLVM reads EnvBlockSym::Fields
using CodeViewRecordIO::mapStringZVectorZ, which consumes
NUL-terminated strings until it encounters an empty string. When
writing, it explicitly terminates the list with a zero byte. See
https://github.com/llvm/llvm-project/blob/61914fd3ce4100d61859471f4fbc48db82185c0a/llvm/lib/DebugInfo/CodeView/CodeViewRecordIO.cpp#L256.
Oleg
On 02/10/2026 6.42 pm, Oleg Tolmatcev wrote:
> пт, 2 окт. 2026 г. в 12:23, Jan Beulich <jbeulich@suse.com>:
>>
>> On 30.09.2026 21:37, Oleg Tolmatcev wrote:
>>> The list of strings in an S_ENVBLOCK symbol is terminated by an empty
>>> string, which we never wrote. When the strings happened to end on a
>>> four byte boundary, there was no alignment padding to stand in for the
>>> terminator, and readers then ran off the end of the record; llvm-pdbutil
>>> reported "Stream Error: The stream is too short to perform the requested
>>> operation".
>>>
>>> ld/
>>> * pdb.c (create_linker_symbols): Terminate the list of strings in
>>> the S_ENVBLOCK symbol.
>>> ---
>>> ld/pdb.c | 3 +++
>>> 1 file changed, 3 insertions(+)
>>
>> Again - looks plausible, but without a pointer to where in the spec this
>> is mandated, I can only again ask: Mark, could you please take a look?
>>
>> Jan
>>
> I will try to answer this one too. LLVM reads EnvBlockSym::Fields
> using CodeViewRecordIO::mapStringZVectorZ, which consumes
> NUL-terminated strings until it encounters an empty string. When
> writing, it explicitly terminates the list with a zero byte. See
> https://github.com/llvm/llvm-project/blob/61914fd3ce4100d61859471f4fbc48db82185c0a/llvm/lib/DebugInfo/CodeView/CodeViewRecordIO.cpp#L256.
>
> Oleg
The problem with anything to do with PDB is that isn't really a spec.
There's the microsoft-pdb repository which doesn't compile, and there's
a cvdump.exe binary which is the authoritative version of llvm-pdbutil.
Oleg, I am happy with these patches if the buggy behaviour is also seen
in cvdump.exe. If not, you may need to double-check that it's not an
issue in llvm-pdbutil. In this particular case rgsz is at least one byte
in Microsoft's version, so I suspect it's right.
typedef struct ENVBLOCKSYM {
unsigned short reclen;
unsigned short rectyp;
struct {
unsigned char fEC : 1;
unsigned char pad : 7;
} flags;
unsigned char rgsz[1]; // Sequence of zero-terminated strings
} ENVBLOCKSYM;
Mark
пн, 5 окт. 2026 г. в 19:50, Mark Harmstone <mark@harmstone.com>:
>
> On 02/10/2026 6.42 pm, Oleg Tolmatcev wrote:
> > пт, 2 окт. 2026 г. в 12:23, Jan Beulich <jbeulich@suse.com>:
> >>
> >> On 30.09.2026 21:37, Oleg Tolmatcev wrote:
> >>> The list of strings in an S_ENVBLOCK symbol is terminated by an empty
> >>> string, which we never wrote. When the strings happened to end on a
> >>> four byte boundary, there was no alignment padding to stand in for the
> >>> terminator, and readers then ran off the end of the record; llvm-pdbutil
> >>> reported "Stream Error: The stream is too short to perform the requested
> >>> operation".
> >>>
> >>> ld/
> >>> * pdb.c (create_linker_symbols): Terminate the list of strings in
> >>> the S_ENVBLOCK symbol.
> >>> ---
> >>> ld/pdb.c | 3 +++
> >>> 1 file changed, 3 insertions(+)
> >>
> >> Again - looks plausible, but without a pointer to where in the spec this
> >> is mandated, I can only again ask: Mark, could you please take a look?
> >>
> >> Jan
> >>
> > I will try to answer this one too. LLVM reads EnvBlockSym::Fields
> > using CodeViewRecordIO::mapStringZVectorZ, which consumes
> > NUL-terminated strings until it encounters an empty string. When
> > writing, it explicitly terminates the list with a zero byte. See
> > https://github.com/llvm/llvm-project/blob/61914fd3ce4100d61859471f4fbc48db82185c0a/llvm/lib/DebugInfo/CodeView/CodeViewRecordIO.cpp#L256.
> >
> > Oleg
>
> The problem with anything to do with PDB is that isn't really a spec.
> There's the microsoft-pdb repository which doesn't compile, and there's
> a cvdump.exe binary which is the authoritative version of llvm-pdbutil.
>
> Oleg, I am happy with these patches if the buggy behaviour is also seen
> in cvdump.exe. If not, you may need to double-check that it's not an
> issue in llvm-pdbutil. In this particular case rgsz is at least one byte
> in Microsoft's version, so I suspect it's right.
>
> typedef struct ENVBLOCKSYM {
> unsigned short reclen;
> unsigned short rectyp;
> struct {
> unsigned char fEC : 1;
> unsigned char pad : 7;
> } flags;
> unsigned char rgsz[1]; // Sequence of zero-terminated strings
> } ENVBLOCKSYM;
>
> Mark
cvdump can't confirm this bug. Its S_ENVBLOCK loop stops at the end of
the record as well as at an empty string. I tested MSVC link.exe and
it always writes the terminator, even when the strings already end on
a 4-byte boundary. So I am sure that llvm-pdbutils is correct.
I tested all of the patches with llvm-pdbutils and cvdump with 4 large
PDB files and they were all successful.
Oleg
@@ -3811,6 +3811,7 @@ create_linker_symbols (bfd *abfd, uint8_t **syms, uint32_t *sym_byte_size,
env_size += strlen (exeval) + 1;
env_size += sizeof (pdb);
env_size += strlen (pdbval) + 1;
+ env_size++; /* Terminating empty string. */
if (env_size % 4)
padding2 = 4 - (env_size % 4);
@@ -3883,6 +3884,8 @@ create_linker_symbols (bfd *abfd, uint8_t **syms, uint32_t *sym_byte_size,
memcpy (ptr, pdbval, strlen (pdbval) + 1);
ptr += strlen (pdbval) + 1;
+ *ptr++ = 0;
+
/* Microsoft's LINK also includes "cmd", the command-line options passed
to the linker, but unfortunately we don't have access to argc and argv
at this stage. */