Подтвердить что ты не робот

Отладка программы C (сервер Redis)

Предпосылка

Привет,

Я получил несколько отчетов от пользователя Redis, у которого возникли сбои сервера, с использованием стабильной версии Redis (последняя версия, 2.4.6). Ошибка странная, поскольку пользователь не делает эзотерические вещи, просто много работает со отсортированным типом набора и только с командами ZADD, ZREM и ZREVRANK. Однако странно, что подобная ошибка, вызвавшая аварии после нескольких миллиардов выполненных операций, была испытана только одним пользователем. К счастью, пользователь, о котором идет речь, очень полезен и много сотрудничает в отслеживании проблемы, поэтому мне удалось получить много раз журналы с точной последовательностью операций, выполняемых Redis, что я повторно воспроизводил локально без результата, я также попытался писать сценарии, чтобы точно имитировать вид рабочей нагрузки, выполнять углубленные обзоры кода реализации списка пропуска и т.д.

Даже после всех этих усилий невозможно воспроизвести проблему на местном уровне. Стоит также упомянуть, что в какой-то момент пользователь начал отправлять тот же самый трафик в другой блок с той же версией Redis, но скомпилирован с другим gcc и работает на разных аппаратных средствах: до сих пор никаких проблем в этом втором экземпляре. Тем не менее я хочу понять, что происходит.

Итак, наконец, я настроил другую стратегию с пользователем и попросил его запустить Redis с помощью gdb, чтобы получить файл ядра. Наконец Redis снова разбился, и теперь у меня есть как основной файл, так и исполняемый файл. К сожалению, я забыл попросить пользователя скомпилировать Redis без оптимизации.

Мне нужна помощь сообщества, так как с GDB я пришел к какому-то выводу, но я действительно не знаю, что здесь может произойти: в какой-то момент функция вычисляет указатель и когда он вызывает другую функцию магически, что указатель отличается, указывая на ячейку памяти, которая не содержит правильный тип данных.

Сеанс GDB

Исходный исполняемый файл был скомпилирован с GCC 4.4.5-8, это сеанс GDB, который показывает мое исследование:

gdb ./redis-server core.16525
GNU gdb (GDB) 7.1-ubuntu
[snip]
Program terminated with signal 11, Segmentation fault.
#0  0x00007f3d9ecd216c in __pthread_rwlock_tryrdlock (rwlock=0x1)
    at pthread_rwlock_tryrdlock.c:46
46      pthread_rwlock_tryrdlock.c: No such file or directory.
        in pthread_rwlock_tryrdlock.c

На самом деле трассировка трассировки связана с тем, что вторичная нить ничего не делает (вы можете смело рассматривать Redis однопоточное приложение, другие потоки используются только для выполнения таких действий, как fsync(), для файлового дескриптора без блокировки), позволяют выбирать правильный.

(gdb) info threads
  3 Thread 16525  zslGetRank (zsl=0x7f3d8d71c360, score=19.498544884710096, 
    o=0x7f3d4cab5760) at t_zset.c:335
  2 Thread 16527  0x00007f3d9ecd216c in __pthread_rwlock_tryrdlock (
    rwlock=0x6b7f5) at pthread_rwlock_tryrdlock.c:46
* 1 Thread 16526  0x00007f3d9ecd216c in __pthread_rwlock_tryrdlock (rwlock=0x1)
    at pthread_rwlock_tryrdlock.c:46
(gdb) thread 3
[Switching to thread 3 (Thread 16525)]#0  zslGetRank (zsl=0x7f3d8d71c360, 
    score=19.498544884710096, o=0x7f3d4cab5760) at t_zset.c:335
335     t_zset.c: No such file or directory.
        in t_zset.c
(gdb) bt
#0  zslGetRank (zsl=0x7f3d8d71c360, score=19.498544884710096, o=0x7f3d4cab5760)
    at t_zset.c:335
#1  0x000000000042818b in zrankGenericCommand (c=0x7f3d9dcdc000, reverse=1)
    at t_zset.c:2046
#2  0x00000000004108d4 in call (c=0x7f3d9dcdc000) at redis.c:1024
#3  0x0000000000410c1c in processCommand (c=0x7f3d9dcdc000) at redis.c:1130
#4  0x0000000000419d3f in processInputBuffer (c=0x7f3d9dcdc000)
    at networking.c:865
#5  0x0000000000419e1c in readQueryFromClient (el=<value optimized out>, 
    fd=<value optimized out>, privdata=0x7f3d9dcdc000, 
    mask=<value optimized out>) at networking.c:908
#6  0x000000000040d4a3 in aeProcessEvents (eventLoop=0x7f3d9dc47000, 
    flags=<value optimized out>) at ae.c:342
#7  0x000000000040d6ee in aeMain (eventLoop=0x7f3d9dc47000) at ae.c:387
#8  0x0000000000412a4f in main (argc=2, argv=<value optimized out>)
    at redis.c:1719

Мы также создали обратную линию. Как вы видите, call() отправляет команду ZREVRANK, поэтому вызов zrankGenericCommand() вызывается с клиентской структурой, а аргумент reverse = 1 (так как это REV rank). Мы можем легко исследовать, какие аргументы команды ZREVRANK.

(gdb) up
#1  0x000000000042818b in zrankGenericCommand (c=0x7f3d9dcdc000, reverse=1)
    at t_zset.c:2046
2046    in t_zset.c
(gdb) print c->argc
$8 = 3
(gdb) print (redisClient*)c->argc
$9 = (redisClient *) 0x3
(gdb) print (char*)(redisClient*)c->argv[0]->ptr
$10 = 0x7f3d8267ce28 "zrevrank"
(gdb) print (char*)(redisClient*)c->argv[1]->ptr
$11 = 0x7f3d8267ce48 "pc_stat.hkperc"
(gdb) print (long)(redisClient*)c->argv[2]->ptr
$12 = 282472606

Таким образом, фактическая команда, создающая аварийную ситуацию, была следующей: ZREVRANK pc_stat.hkperc 282472606 Это согласуется с журналами клиентов, полученными пользователем. Обратите внимание, что я навел указатель на длинное целое число для последнего аргумента, так как Redis кодирует целые числа таким образом, чтобы сохранить память, когда это возможно.

Теперь, когда это хорошо, настало время исследовать zrankGenericCommand(), который вызвал zslGetRan(), который вызвал фактический сбой. Это исходный код C zrankGenericCommand около 2046:

  2036      } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
  2037          zset *zs = zobj->ptr;
  2038          zskiplist *zsl = zs->zsl;
  2039          dictEntry *de;
  2040          double score;
  2041  
  2042          ele = c->argv[2] = tryObjectEncoding(c->argv[2]);
  2043          de = dictFind(zs->dict,ele);
  2044          if (de != NULL) {
  2045              score = *(double*)dictGetEntryVal(de);
  2046              rank = zslGetRank(zsl,score,ele);
  2047              redisAssert(rank); /* Existing elements always have a rank. */
  2048              if (reverse)
  2049                  addReplyLongLong(c,llen-rank);
  2050              else
  2051                  addReplyLongLong(c,rank-1);
  2052          } else {
  2053              addReply(c,shared.nullbulk);
  2054          }
  2055      }

Хорошо, вот как это работает:

  • Мы просматриваем ключ Redis, содержащий тип данных отсортированного набора (поиск не входит в код). Объект Redis, связанный с ключом, сохраняется в локальной переменной zobj.
  • Поле ptr zobj - это указатель на структуру типа zset, представляющую отсортированный набор.
  • В свою очередь структура zset имеет два указателя, один указывает на хеш-таблицу и один на список пропуска. Это необходимо, так как мы оба предоставляем поиск по элементам в O (1), для которого нам нужна хеш-таблица, но также мы берем упорядоченные элементы, поэтому мы используем список пропусков. В строке 2038 указатель на список пропуска (представлен структурой zskiplist) присваивается переменной zsl.
  • Позже мы кодируем третий аргумент (строка 2042), поэтому мы вывели значение в long, чтобы напечатать его из структуры клиента.
  • В строке 2043 мы просматриваем элемент из словаря, и операция завершается успешно, так как мы знаем, что функция zslGetRank() стороны вызывается ветвью , если.
  • Наконец, в строке 2046 мы вызываем zslGetRank() с тремя аргументами: указателем на список пропусков, счетом элемента и самим элементом.

Прекрасно... теперь что такое указатель, который zslGetRank() должен получить теоретически? Мы можем легко исследовать это вручную, глядя на таблицу хэша Redis. Я хэшировал вручную ключ и сопоставлял его с ведром 62 хеш-таблицы, давая понять, верно ли это:

(gdb) print (char*)c->db->dict->ht->table[62]->key
$13 = 0x7f3d9dc0f6c8 "pc_stat.hkperc"

Точно так, как ожидалось. Пусть проверяется связанный объект:

(gdb) print *(robj*)c->db->dict->ht->table[62]->val
$16 = {type = 3, storage = 0, encoding = 7, lru = 557869, refcount = 1, 
  ptr = 0x7f3d9de574b0}

Тип = 3, Кодирование = 7, это означает: это отсортированный набор, закодированный как список пропусков. Прекрасно. Сортированный адрес набора (поле ptr) равен 0x7f3d9de574b0, поэтому мы также можем это проверить:

(gdb) print *(zset*)0x7f3d9de574b0
$17 = {dict = 0x7f3d9dcf6c20, zsl = 0x7f3d9de591c0}

Итак, мы имеем:

  • Объект, связанный с ключом, который указывает на сортированный набор, который хранится по адресу 0x7f3d9de574b0
  • В свою очередь этот отсортированный набор реализуется с помощью skiplist по адресу 0x7f3d9de591c0 (zsl field)

Теперь проверьте, установлены ли наши две переменные в правильные значения:

2037            zset *zs = zobj->ptr;
2038            zskiplist *zsl = zs->zsl;

(gdb) info locals
zs = 0x7f3d9de574b0
zsl = 0x7f3d9de591c0
de = <value optimized out>
ele = <value optimized out>
zobj = <value optimized out>
llen = 165312
rank = <value optimized out>

До сих пор все идеально: переменная zs установлена ​​в 0x7f3d9de574b0, как и ожидалось, а также переменная zsl, указывающая на skiplist, которая установлена ​​в 0x7f3d9de591c0.

Теперь эти переменные не затрагиваются в ходе выполнения кода:

Это единственные строки кода между назначением переменных и вызовом функции zslGetRank():

2042            ele = c->argv[2] = tryObjectEncoding(c->argv[2]);
2043            de = dictFind(zs->dict,ele);
2044            if (de != NULL) {
2045                score = *(double*)dictGetEntryVal(de);
2046                rank = zslGetRank(zsl,score,ele);

Никто не трогает zsl, но если мы проверим трассировку стека, мы увидим, что функция zslGetRank() вызывается не с адресом 0x7f3d9de591c0 в качестве первого аргумента, а с другим:

#0  zslGetRank (zsl=0x7f3d8d71c360, score=19.498544884710096, o=0x7f3d4cab5760)
    at t_zset.c:335

Если вы все это прочитаете, вы являетесь героем, и вознаграждение очень мало, и в этом вопросе: есть ли у вас идея, даже учитывая, что отказ в аппаратном обеспечении является вариантом, как изменить этот аргумент? Кажется очень маловероятным, что функция кодирования объекта или поиск таблицы хэшей могут привести к повреждению стека вызывающего (но, по-видимому, аргумент находится внутри регистров уже во время вызова). Мой ассемблер невелик, поэтому, если у вас есть какая-то подсказка... это очень приветствуется. Я оставлю вас с выходом информационных регистров и разобрав:

(gdb) info registers
rax            0x6      6
rbx            0x7f3d9dcdc000   139902617239552
rcx            0xf742d0b6       4148351158
rdx            0x7f3d95efada0   139902485245344
rsi            0x7f3d4cab5760   139901256030048
rdi            0x7f3d8d71c360   139902342775648
rbp            0x7f3d4cab5760   0x7f3d4cab5760
rsp            0x7fffe61a8040   0x7fffe61a8040
r8             0x7fffe61a7fd9   140737053884377
r9             0x1      1
r10            0x7f3d9dcd4ff0   139902617210864
r11            0x6      6
r12            0x1      1
r13            0x7f3d9de574b0   139902618793136
r14            0x7f3d9de591c0   139902618800576
r15            0x7f3d8267c9e0   139902157572576
rip            0x42818b 0x42818b <zrankGenericCommand+251>
eflags         0x10206  [ PF IF RF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) disassemble zrankGenericCommand
Dump of assembler code for function zrankGenericCommand:
   0x0000000000428090 <+0>:     mov    %rbx,-0x30(%rsp)
   0x0000000000428095 <+5>:     mov    %r12,-0x20(%rsp)
   0x000000000042809a <+10>:    mov    %esi,%r12d
   0x000000000042809d <+13>:    mov    %r14,-0x10(%rsp)
   0x00000000004280a2 <+18>:    mov    %rbp,-0x28(%rsp)
   0x00000000004280a7 <+23>:    mov    %rdi,%rbx
   0x00000000004280aa <+26>:    mov    %r13,-0x18(%rsp)
   0x00000000004280af <+31>:    mov    %r15,-0x8(%rsp)
   0x00000000004280b4 <+36>:    sub    $0x58,%rsp
   0x00000000004280b8 <+40>:    mov    0x28(%rdi),%rax
   0x00000000004280bc <+44>:    mov    0x23138d(%rip),%rdx        # 0x659450 <shared+80>
   0x00000000004280c3 <+51>:    mov    0x8(%rax),%rsi
   0x00000000004280c7 <+55>:    mov    0x10(%rax),%rbp
   0x00000000004280cb <+59>:    callq  0x41d370 <lookupKeyReadOrReply>
   0x00000000004280d0 <+64>:    test   %rax,%rax
   0x00000000004280d3 <+67>:    mov    %rax,%r14
   0x00000000004280d6 <+70>:    je     0x4280ec <zrankGenericCommand+92>
   0x00000000004280d8 <+72>:    mov    $0x3,%edx
   0x00000000004280dd <+77>:    mov    %rax,%rsi
   0x00000000004280e0 <+80>:    mov    %rbx,%rdi
   0x00000000004280e3 <+83>:    callq  0x41b270 <checkType>
   0x00000000004280e8 <+88>:    test   %eax,%eax
   0x00000000004280ea <+90>:    je     0x428110 <zrankGenericCommand+128>
   0x00000000004280ec <+92>:    mov    0x28(%rsp),%rbx
   0x00000000004280f1 <+97>:    mov    0x30(%rsp),%rbp
   0x00000000004280f6 <+102>:   mov    0x38(%rsp),%r12
   0x00000000004280fb <+107>:   mov    0x40(%rsp),%r13
   0x0000000000428100 <+112>:   mov    0x48(%rsp),%r14
   0x0000000000428105 <+117>:   mov    0x50(%rsp),%r15
   0x000000000042810a <+122>:   add    $0x58,%rsp
   0x000000000042810e <+126>:   retq   
   0x000000000042810f <+127>:   nop
   0x0000000000428110 <+128>:   mov    %r14,%rdi
   0x0000000000428113 <+131>:   callq  0x426250 <zsetLength>
   0x0000000000428118 <+136>:   testw  $0x3c0,0x0(%rbp)
   0x000000000042811e <+142>:   jne    0x4282b7 <zrankGenericCommand+551>
   0x0000000000428124 <+148>:   mov    %eax,%eax
   0x0000000000428126 <+150>:   mov    %rax,0x8(%rsp)
   0x000000000042812b <+155>:   movzwl (%r14),%eax
   0x000000000042812f <+159>:   and    $0x3c0,%ax
   0x0000000000428133 <+163>:   cmp    $0x140,%ax
   0x0000000000428137 <+167>:   je     0x4281c8 <zrankGenericCommand+312>
   0x000000000042813d <+173>:   cmp    $0x1c0,%ax
   0x0000000000428141 <+177>:   jne    0x428299 <zrankGenericCommand+521>
   0x0000000000428147 <+183>:   mov    0x28(%rbx),%r15
   0x000000000042814b <+187>:   mov    0x8(%r14),%r13
   0x000000000042814f <+191>:   mov    0x10(%r15),%rdi
   0x0000000000428153 <+195>:   mov    0x8(%r13),%r14
   0x0000000000428157 <+199>:   callq  0x41bcc0 <tryObjectEncoding>
   0x000000000042815c <+204>:   mov    0x0(%r13),%rdi
   0x0000000000428160 <+208>:   mov    %rax,0x10(%r15)
   0x0000000000428164 <+212>:   mov    %rax,%rsi
   0x0000000000428167 <+215>:   mov    %rax,%rbp
   0x000000000042816a <+218>:   callq  0x40ede0 <dictFind>
   0x000000000042816f <+223>:   test   %rax,%rax
   0x0000000000428172 <+226>:   je     0x428270 <zrankGenericCommand+480>
   0x0000000000428178 <+232>:   mov    0x8(%rax),%rax
   0x000000000042817c <+236>:   mov    %rbp,%rsi
   0x000000000042817f <+239>:   mov    %r14,%rdi
   0x0000000000428182 <+242>:   movsd  (%rax),%xmm0
   0x0000000000428186 <+246>:   callq  0x427fd0 <zslGetRank>
=> 0x000000000042818b <+251>:   test   %rax,%rax
   0x000000000042818e <+254>:   je     0x4282d5 <zrankGenericCommand+581>
   0x0000000000428194 <+260>:   test   %r12d,%r12d
   0x0000000000428197 <+263>:   je     0x4281b0 <zrankGenericCommand+288>
   0x0000000000428199 <+265>:   mov    0x8(%rsp),%rsi
   0x000000000042819e <+270>:   mov    %rbx,%rdi
   0x00000000004281a1 <+273>:   sub    %rax,%rsi
   0x00000000004281a4 <+276>:   callq  0x41a430 <addReplyLongLong>
   0x00000000004281a9 <+281>:   jmpq   0x4280ec <zrankGenericCommand+92>
   0x00000000004281ae <+286>:   xchg   %ax,%ax
   0x00000000004281b0 <+288>:   lea    -0x1(%rax),%rsi
   0x00000000004281b4 <+292>:   mov    %rbx,%rdi
   0x00000000004281b7 <+295>:   callq  0x41a430 <addReplyLongLong>
   0x00000000004281bc <+300>:   nopl   0x0(%rax)
   0x00000000004281c0 <+304>:   jmpq   0x4280ec <zrankGenericCommand+92>
   0x00000000004281c5 <+309>:   nopl   (%rax)
   0x00000000004281c8 <+312>:   mov    0x8(%r14),%r14
   0x00000000004281cc <+316>:   xor    %esi,%esi
   0x00000000004281ce <+318>:   mov    %r14,%rdi
   0x00000000004281d1 <+321>:   callq  0x417600 <ziplistIndex>
   0x00000000004281d6 <+326>:   test   %rax,%rax
   0x00000000004281d9 <+329>:   mov    %rax,0x18(%rsp)
   0x00000000004281de <+334>:   je     0x428311 <zrankGenericCommand+641>
   0x00000000004281e4 <+340>:   mov    %rax,%rsi
   0x00000000004281e7 <+343>:   mov    %r14,%rdi
   0x00000000004281ea <+346>:   callq  0x4175c0 <ziplistNext>
   0x00000000004281ef <+351>:   test   %rax,%rax
   0x00000000004281f2 <+354>:   mov    %rax,0x10(%rsp)
   0x00000000004281f7 <+359>:   je     0x4282f3 <zrankGenericCommand+611>
   0x00000000004281fd <+365>:   mov    0x18(%rsp),%rdi
   0x0000000000428202 <+370>:   mov    $0x1,%r13d
   0x0000000000428208 <+376>:   lea    0x10(%rsp),%r15
   0x000000000042820d <+381>:   test   %rdi,%rdi
   0x0000000000428210 <+384>:   jne    0x428236 <zrankGenericCommand+422>
   0x0000000000428212 <+386>:   jmp    0x428270 <zrankGenericCommand+480>
   0x0000000000428214 <+388>:   nopl   0x0(%rax)
   0x0000000000428218 <+392>:   lea    0x18(%rsp),%rsi
   0x000000000042821d <+397>:   mov    %r14,%rdi
   0x0000000000428220 <+400>:   mov    %r15,%rdx
   0x0000000000428223 <+403>:   callq  0x425610 <zzlNext>
   0x0000000000428228 <+408>:   mov    0x18(%rsp),%rdi
   0x000000000042822d <+413>:   test   %rdi,%rdi
   0x0000000000428230 <+416>:   je     0x428270 <zrankGenericCommand+480>
   0x0000000000428232 <+418>:   add    $0x1,%r13
   0x0000000000428236 <+422>:   mov    0x8(%rbp),%rsi
   0x000000000042823a <+426>:   movslq -0x8(%rsi),%rdx
   0x000000000042823e <+430>:   callq  0x417a40 <ziplistCompare>
   0x0000000000428243 <+435>:   test   %eax,%eax
   0x0000000000428245 <+437>:   je     0x428218 <zrankGenericCommand+392>
   0x0000000000428247 <+439>:   cmpq   $0x0,0x18(%rsp)
   0x000000000042824d <+445>:   je     0x428270 <zrankGenericCommand+480>
   0x000000000042824f <+447>:   test   %r12d,%r12d
   0x0000000000428252 <+450>:   je     0x428288 <zrankGenericCommand+504>
   0x0000000000428254 <+452>:   mov    0x8(%rsp),%rsi
   0x0000000000428259 <+457>:   mov    %rbx,%rdi
   0x000000000042825c <+460>:   sub    %r13,%rsi
   0x000000000042825f <+463>:   callq  0x41a430 <addReplyLongLong>
   0x0000000000428264 <+468>:   jmpq   0x4280ec <zrankGenericCommand+92>
   0x0000000000428269 <+473>:   nopl   0x0(%rax)
   0x0000000000428270 <+480>:   mov    0x2311d9(%rip),%rsi        # 0x659450 <shared+80>
   0x0000000000428277 <+487>:   mov    %rbx,%rdi
   0x000000000042827a <+490>:   callq  0x419f60 <addReply>
   0x000000000042827f <+495>:   jmpq   0x4280ec <zrankGenericCommand+92>
   0x0000000000428284 <+500>:   nopl   0x0(%rax)
   0x0000000000428288 <+504>:   lea    -0x1(%r13),%rsi
   0x000000000042828c <+508>:   mov    %rbx,%rdi
   0x000000000042828f <+511>:   callq  0x41a430 <addReplyLongLong>
   0x0000000000428294 <+516>:   jmpq   0x4280ec <zrankGenericCommand+92>
   0x0000000000428299 <+521>:   mov    $0x44939f,%edi
   0x000000000042829e <+526>:   mov    $0x808,%edx
   0x00000000004282a3 <+531>:   mov    $0x44a674,%esi
   0x00000000004282a8 <+536>:   callq  0x432010 <_redisPanic>
   0x00000000004282ad <+541>:   mov    $0x1,%edi
   0x00000000004282b2 <+546>:   callq  0x40c3a0 <[email protected]>
   0x00000000004282b7 <+551>:   mov    $0x44a7d0,%edi
   0x00000000004282bc <+556>:   mov    $0x7da,%edx
   0x00000000004282c1 <+561>:   mov    $0x44a674,%esi
   0x00000000004282c6 <+566>:   callq  0x432090 <_redisAssert>
   0x00000000004282cb <+571>:   mov    $0x1,%edi
   0x00000000004282d0 <+576>:   callq  0x40c3a0 <[email protected]>
   0x00000000004282d5 <+581>:   mov    $0x448982,%edi
   0x00000000004282da <+586>:   mov    $0x7ff,%edx
   0x00000000004282df <+591>:   mov    $0x44a674,%esi
   0x00000000004282e4 <+596>:   callq  0x432090 <_redisAssert>
   0x00000000004282e9 <+601>:   mov    $0x1,%edi
   0x00000000004282ee <+606>:   callq  0x40c3a0 <[email protected]>
   0x00000000004282f3 <+611>:   mov    $0x44a6e5,%edi
   0x00000000004282f8 <+616>:   mov    $0x7e2,%edx
   0x00000000004282fd <+621>:   mov    $0x44a674,%esi
   0x0000000000428302 <+626>:   callq  0x432090 <_redisAssert>
   0x0000000000428307 <+631>:   mov    $0x1,%edi
   0x000000000042830c <+636>:   callq  0x40c3a0 <[email protected]>
   0x0000000000428311 <+641>:   mov    $0x44a6bd,%edi
   0x0000000000428316 <+646>:   mov    $0x7e0,%edx
   0x000000000042831b <+651>:   mov    $0x44a674,%esi
   0x0000000000428320 <+656>:   callq  0x432090 <_redisAssert>
   0x0000000000428325 <+661>:   mov    $0x1,%edi
   0x000000000042832a <+666>:   callq  0x40c3a0 <[email protected]>
   End of assembler dump.

Как запрошено, это функция tryObjectEncoding:

/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {
    long value;
    sds s = o->ptr;

    if (o->encoding != REDIS_ENCODING_RAW)
        return o; /* Already encoded */

    /* It not safe to encode shared objects: shared objects can be shared
     * everywhere in the "object space" of Redis. Encoded objects can only
     * appear as "values" (and not, for instance, as keys) */
     if (o->refcount > 1) return o;

    /* Currently we try to encode only strings */
    redisAssert(o->type == REDIS_STRING);

    /* Check if we can represent this string as a long integer */
    if (!string2l(s,sdslen(s),&value)) return o;

    /* Ok, this object can be encoded...
     *
     * Can I use a shared object? Only if the object is inside a given
     * range and if this is the main thread, since when VM is enabled we
     * have the constraint that I/O thread should only handle non-shared
     * objects, in order to avoid race conditions (we don't have per-object
     * locking).
     *
     * Note that we also avoid using shared integers when maxmemory is used
     * because very object needs to have a private LRU field for the LRU
     * algorithm to work well. */
    if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS &&
        pthread_equal(pthread_self(),server.mainthread)) {
        decrRefCount(o);
        incrRefCount(shared.integers[value]);
        return shared.integers[value];
    } else {
        o->encoding = REDIS_ENCODING_INT;
        sdsfree(o->ptr);
        o->ptr = (void*) value;
        return o;
    }
}
4b9b3361

Ответ 1

Обновление: оперативная память в этом поле была сломана, мы обнаружили много проблем в пользовательской ОЗУ после этого, и теперь Redis даже реализует опцию --test-memory... Спасибо.

Ответ 2

Думаю, теперь я могу ответить на свой вопрос...

в основном это то, что происходит. zslGetRank() вызывается zrankGenericCommand() с первым аргументом в регистр% rdi. Однако позже эта функция будет использовать регистр% rdi для установки объекта (и, действительно, для регистра% rdi установлен действительный объект):

(gdb) print *(robj*)0x7f3d8d71c360
$1 = {type = 0, storage = 0, encoding = 1, lru = 517611, refcount = 2, 
ptr = 0x1524db19}

Указатель инструкции фактически указывал на zslGetRank + 64 во время сбоя, я сделал что-то не так с gdb и изменил регистр перед отправкой исходного вопроса.

Также как проверить, что zslGetRank() получает правильный адрес в качестве первого аргумента? Поскольку% r14 сохраняется в стеке zslGetRank(), мы можем проверить стек, чтобы проверить, есть ли правильное местоположение. Итак, мы дамп возле указателя стека:

0x7fffe61a8000: 0x40337fa0a3376aff      0x00007f3d9dcdc000
0x7fffe61a8010: 0x00007f3d9dcdc000      0x00007f3d4cab5760
0x7fffe61a8020: 0x0000000000000001      0x00007f3d9de574b0
---> 0x7fffe61a8030: 0x00007f3d9de591c0      0x000000000042818b
0x7fffe61a8040: 0x0000000000000000      0x00000000000285c0
0x7fffe61a8050: 0x0000000000000000      0x00007f3d9dcdc000
0x7fffe61a8060: 0x0000000000000000      0x00007f3d9dcdc000
0x7fffe61a8070: 0x0000000000000000      0x0004b6b413e12d9a
0x7fffe61a8080: 0x00000000000003d8      0x0000000000000001

Как вы видите, правый адрес находится здесь, в стеке.

Короче говоря, функция вызывается с правильным адресом, просто gdb не может сбрасывать правильную трассировку стека, потому что регистр% rdi модифицируется и используется для другой вещи внутри функции.

Таким образом, возможно, это может быть проблема с коррупцией. Теперь я должен выполнить отсортированный набор вручную, имитируя работу zslGetRank(), чтобы я мог изолировать node, который был сломан, и, надеюсь, посмотрим, как он поврежден.

Спасибо за вашу помощь.

Изменить: здесь вы можете найти вручную аннотированную дизассемблированную версию функции zslGetRank() → https://gist.github.com/1641112 (я использовал ее как для изучения еще нескольких ассемблеров, так и для сделайте мой осмотр проще).

Ответ 3

В этой ситуации первое, что я сделаю, это использовать valgrind. Недостатком является то, что valgrind примерно на x10 медленнее, чем собственный, и может изменить поведение, потому что кажется, что он сериализует потоки. Но это спасает меня так много раз!

Во всяком случае, что касается этого сбоя, он встречается в потоке 3, pthread_rwlock_tryrdlock() получает плохой указатель (rwlock равен 0x1). Возможно, это повреждение памяти, вызванное другими потоками. Если это возможно, попробуйте поставить "часы" на эту пои

Надеюсь, что это поможет.

Ответ 4

Его длинный снимок с интуитивной догадкой, но единственное, что я мог увидеть, вызывая эту ошибку, - это назначение указателей в строке 2042:

2042   ele = c->argv[2] = tryObjectEncoding(c->argv[2]);

Надеюсь, что поможет

Ответ 5

Можете ли вы добавить разборку для zslGetRank?

Если вы посмотрите на другое, r14 имеет правильное значение, а rdi имеет неправильное значение, но перед вызовом появляется "mov r14, rdi", поэтому предположительно zslGetRank вызывается с правильными значениями.