On Mon, May 02, 2016 at 12:01:18AM -0400, James McCoy wrote:
> Thanks for the analysis and working on a patch.  I'll note that
> _Alignas, if it had worked, would likely have been too new a compiler
> feature to use.
> 
> It looks like Debian's ia64 porterbox is still running, so I can try
> reproducing the problem there (using prctl to force SIGBUS) and then
> fiddling around with the code.

Here goes further analysis.  Running utf-test under gdb I set a break
point at line 807 of utf-test.c and after a number of iterations of
the enclosing loop I get:

Breakpoint 1, test_utf_conversions (pool=0x20001018028)
    at 
/extra/mjc/debian/subversion/subversion-1.9.4/subversion/tests/libsvn_subr/utf-test.c:807
807           SVN_ERR_ASSERT(0 == strcmp(result->data, tc->result));
(gdb) print tc->source
$20 = 0x120003117 "\330\064\335\036"
(gdb) print result->data
$21 = 0x20001018218 "\360\220\204\236"
(gdb) print tc->result
$22 = 0x12000373c "\360\235\204\236"
(gdb) cont
Continuing.
svn_tests: E235000: In file 
'/extra/mjc/debian/subversion/subversion-1.9.4/subversion/tests/libsvn_subr/utf-test.c'
 line 807: assertion failed (0 == strcmp(result->data, tc->result))
FAIL:  lt-utf-test 9: test svn_utf__utf{16,32}_to_utf8
[Inferior 1 (process 24827) exited with code 01]



On this iteration I believe it has just read line 769 of utf-test.c,
i.e., this line:

    { UTF_16_BE, "\xD8\x34" "\xDD\x1E" "\0\0", "\xf0\x9d\x84\x9e" }, /* 
U+01D11E */

into variable tc, and it passes tc->source (which is at address
0x120003117, i.e., misaligned for apr_uint16_t type) to
svn_utf__utf16_to_utf8() and the result is incorrect and results in
the failed assert.

On previous iterations of the test loop it happened to pass the test
even with misaligned tc->source strings, and it is instructive to
disassemble svn_utf__utf16_to_utf8() to see why this can happen:

1040    svn_error_t *
1041    svn_utf__utf16_to_utf8(const svn_string_t **result,
1042                           const apr_uint16_t *utf16str,
1043                           apr_size_t utf16len,
1044                           svn_boolean_t big_endian,
1045                           apr_pool_t *result_pool,
1046                           apr_pool_t *scratch_pool)
1047    {
   0x00000200000ed990 <+0>:     ldah    gp,12(t12)
   0x00000200000ed994 <+4>:     lda     gp,10976(gp)
   0x00000200000ed998 <+8>:     lda     sp,-160(sp)
   0x00000200000ed9a0 <+16>:    stq     s0,8(sp)
   0x00000200000ed9a4 <+20>:    mov     a1,s0
   0x00000200000ed9a8 <+24>:    stq     s1,16(sp)
   0x00000200000ed9ac <+28>:    mov     a2,s1
   0x00000200000ed9b0 <+32>:    stq     ra,0(sp)
   0x00000200000ed9b4 <+36>:    stq     s2,24(sp)
   0x00000200000ed9b8 <+40>:    stq     s3,32(sp)
   0x00000200000ed9bc <+44>:    stq     s4,40(sp)
   0x00000200000ed9c0 <+48>:    stq     s5,48(sp)
   0x00000200000ed9c4 <+52>:    stq     fp,56(sp)
   0x00000200000ed9c8 <+56>:    stq     a0,152(sp)
   0x00000200000ed9cc <+60>:    stq     a3,128(sp)
   0x00000200000ed9d0 <+64>:    stq     a4,144(sp)

1048      static const apr_uint16_t endiancheck = 0xa55a;
1049      const svn_boolean_t arch_big_endian =
1050        (((const char*)&endiancheck)[sizeof(endiancheck) - 1] == '\x5a');
1051      const svn_boolean_t swap_order = (!big_endian != !arch_big_endian);
1052    
1053      apr_uint16_t lead_surrogate;
1054      apr_size_t length;
1055      apr_size_t offset;
1056      svn_membuf_t ucs4buf;
1057      svn_membuf_t resultbuf;
1058      svn_string_t *res;
1059    
1060      if (utf16len == SVN_UTF__UNKNOWN_LENGTH)
   0x00000200000ed99c <+12>:    lda     t0,1(a2)
   0x00000200000ed9d4 <+68>:    beq     t0,0x200000edbd0 
<svn_utf__utf16_to_utf8+576>

1061        {
1062          const apr_uint16_t *endp = utf16str;
1063          while (*endp++)
   0x00000200000edbd0 <+576>:   mov     a1,s1
   0x00000200000edbd4 <+580>:   unop
   0x00000200000edbe0 <+592>:   lda     s1,2(s1)
   0x00000200000edbe4 <+596>:   lda     t1,-2(s1)
   0x00000200000edbe8 <+600>:   ldq_u   t0,-2(s1)
   0x00000200000edbec <+604>:   extwl   t0,t1,t0
   0x00000200000edbf0 <+608>:   bne     t0,0x200000edbe0 
<svn_utf__utf16_to_utf8+592>

1064            ;


In the loop immediately above (lines 1061--1064 of the source code)
we see the code is walking through the utf16 string (register a1 holds
argument 1, i.e., utf16str, which is first copied to register s1 and
used as the pointer incremented in the loop).  The load instructions
to get the utf16 character are the two instructions:

   0x00000200000edbe8 <+600>:   ldq_u   t0,-2(s1)
   0x00000200000edbec <+604>:   extwl   t0,t1,t0

The ldq_u zeroes the lowest three bits of the address and loads the
quadword (64-bit word) from the resultant address, and the extwl
extracts the 16-bit word identified by the lowest three bits of the
address (which are in register t1) from the quadword in register t0,
however this is only correct if the lowest three bits are not 0x7.

If the source string address has the lowest three bits all set then,
and only then, the generated code loads incorrect utf16 chars.

This is a reasonable optimisation of the code made by gcc as the
argument utf16str is declared to be a pointer to apr_uint16_t
type, i.e., data that are 16-bit aligned, thus gcc can reason that
an address with the lowest three bits set is not possible.

Cheers
Michael.

Reply via email to