неверный адрес (0x0) виртуального метода - PullRequest
0 голосов
/ 03 декабря 2018

В некоторых кодах есть странные случайные ошибки в коде, вызывающие виртуальные функции-члены.Segfault происходит приблизительно в среднем один раз в вызовах по 30 тыс.

Я использую виртуальные методы для реализации шаблона шаблонного метода.

Строка кода, в которой это происходит, является первой строкой

GenericDevice::updateValue()
{
     ...
     double tmpValue=getValue();
     Value=tmpValue;
     ...
}

с

class GenericDevice
{
    public: 
    void updateValue();
    void print(string& result);
    ...
    protected:
    virtual double getValue()const=0;
    ...
    private:
    std::atomic<double> Value;
    ...
}

Класс GenericDevice предоставляется позже путем загрузки динамической библиотеки во время выполнения

class SpecializedDeviced : public
{
    ...
    virtual double getValue()const final;
    ... 
}

Я смог получить coredump, когда возникла проблема, и посмотрел код сборки:

0x55cd3ef036f4 GenericDevice::updateValue()+92   mov    -0x38(%rbp),%rax   
0x55cd3ef036f8 GenericDevice::updateValue()+96   mov    (%rax),%rax 
0x55cd3ef036fb GenericDevice::updateValue()+99   add    $0x40,%rax  
0x55cd3ef036ff GenericDevice::updateValue()+103  mov    (%rax),%rax 
0x55cd3ef03702 GenericDevice::updateValue()+106  mov   -0x38(%rbp),%rdx
0x55cd3ef03706 GenericDevice::updateValue()+110  mov   %rdx,%rdi         
0x55cd3ef03709 GenericDevice::updateValue()+113  callq  *%rax
0x55cd3ef0370b <GenericDevice::updateValue()+115>  movq   %xmm0,%rax          
0x55cd3ef03710 <GenericDevice::updateValue()+120>  mov    %rax,-0x28(%rbp) 
0x55cd3ef03714 <GenericDevice::updateValue()+124>  mov    -0x38(%rbp),%rax  
0x55cd3ef03718 <GenericDevice::updateValue()+128>  lea    0x38(%rax),%rdx     
0x55cd3ef0371c <GenericDevice::updateValue()+132>  mov    -0x28(%rbp),%rax    
0x55cd3ef03720 <GenericDevice::updateValue()+136>  mov    %rax,-0x40(%rbp)    
0x55cd3ef03724 <GenericDevice::updateValue()+140>  movsd  -0x40(%rbp),%xmm0  

Ожидается, что segfault произошла в 0x55cd3ef03709 GenericDevice :: updateValue () + 113,

where
#0  0x000055cd3ef0370a in MyNamespace::GenericDevice::updateValue (this=0x55cd40586698) at ../src/GenericDevice.cpp:22
#1  0x000055cd3ef038d2 in MyNamespace::GenericDevice::print (this=0x55cd40586698,result="REDACTED"...) at ../src/GenericDevice.cpp:50
...

Функция GenericDevice :: updateValue () была вызвана так, как и предполагалось

<GenericDevice::print(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&)+301>  callq  0x55cd3ef03698 <GenericDevice::updateValue()>

Причина, по которой rax установлен в 0x0.

Register group: general
rax            0x0              0  
rbx            0x5c01b8a2       1543616674  
rcx            0x2              2  
rdx            0x28             40  
rsi            0x2              2  
rdi            0x55cd40586630   94340036191792  
rbp            0x7ffe39086e60   0x7ffe39086e60  
rsp            0x7ffe39086e20   0x7ffe39086e20  
r8             0x7fbb06e7e8a0   140441251473568  
r9             0x3              3  
r10            0x33             51  
r11            0x206            518                       
r12            0x55cd3ef19438   94340012676152  
r13            0x7ffe39089010   140729855283216   
r14            0x0              0   
r15            0x0              0  
rip            0x55cd3ef0370a  0x55cd3ef0370a<GenericDevice::updateValue()+114>                     eflags         0x10206  [ PF IF RF ]               
cs             0x33     51
ss             0x2b     43
ds             0x0      0  
es             0x0      0  
fs             0x0      0   
gs             0x0      0 

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

  1. этот указатель объекта используется

    (gdb) x /g $rbp-0x38  
    0x7ffe39086e28: 0x000055cd40586698   
    (gdb) p this  
    $1 = (GenericDevice * const) 0x55cd40586698
    
  2. указатель на vtable правильный (первый элемент * this)

    (gdb) x 0x000055cd40586698  
    0x55cd40586698: 0x00007fbb070c1aa0
    (gdb) info vtbl this  
    vtable for 'GenericDevice' @ 0x7fbb070c1aa0 (subobject @ 0x55cd40586698):
    
  3. vtable содержит адрес метода, который мы ищем.

    (gdb) info vtbl this  
    vtable for 'GenericDevice' @ 0x7fbb070c1aa0 (subobject @ 0x55cd40586698):  
    ...  
    [8]: 0x7fbb06e7bf50 non-virtual thunk to MyNamespace::SpecializedDevice::getValue() const.
    
  4. используется правильное смещение для vtable

    (gdb) x 0x00007fbb070c1aa0+0x40  
    0x7fbb070c1ae0 <_ZTVN12MyNamespace11SpecializedDeviceE+168>: 0x00007fbb06e7bf50
    

Заключение на данный момент: пошаговое выполнение кода ассемблера позволило проверить правильность данных и инструкций.

  • Использовались правильные данные: повреждение памяти может быть исключено.
  • Инструкции по сборке кажутся правильными: ошибка кодирования / компиляции может быть исключена
  • vtable выглядит нормально: ошибка при загрузке библиотеки во время выполнения может быть исключена: также функция обычно работает нормально десятки тысяч раз.

Пожалуйста, не стесняйтесь указывать на любые ошибки в моих рассуждениях.

Тем не менее значение в регистре rax равно нулю вместо ожидаемого 0x7fbb070c1ae0

  • Может ли это указывать на аппаратную ошибку в одном (редко используемом) ядре процессора?Объяснил бы редкий и случайный случай, но я ожидал бы проблем с другими программами и ОС также.

Модель процессора - процессор Intel® Core (TM) i7-4770 @ 3,40 ГГц

Заранее спасибо!

Обновление: я нашел $Маркер RIP
0x55cd3ef0370a MyNamespace::GenericDevice::updateValue()+114 shlb 0x48(%rsi)

Сборка, показанная gdb, похоже, меняется после прокрутки.Вот почему я не увидел маркер с первой попытки.После запуска GDB и ввода макета asm я получаю:

>0x55cd3ef0370a <MyNamespace::GenericDevicer::updateValue()+114>  shlb   0x48(%rsi)           
0x55cd3ef0370d <MyNamespace::GenericDevicer::updateValue()+117>  movd   %mm0,%eax            
0x55cd3ef03710 <MyNamespace::GenericDevicer::updateValue()+120>  mov    %rax,-0x28(%rbp)     
0x55cd3ef03714 <MyNamespace::GenericDevicer::updateValue()+124>  mov    -0x38(%rbp),%rax     
0x55cd3ef03718 <MyNamespace::GenericDevicer::updateValue()+128>  lea    0x38(%rax),%rdx   
0x55cd3ef0371c <MyNamespace::GenericDevicer::updateValue()+132>  mov    -0x28(%rbp),%rax
0x55cd3ef03720 <MyNamespace::GenericDevicer::updateValue()+136>  mov    %rax,-0x40(%rbp)
0x55cd3ef03724 <MyNamespace::GenericDevicer::updateValue()+140>  movsd  -0x40(%rbp),%xmm0  

...

После прокрутки AMS в GDB я получаю код, отправленный в исходном вопросе.Код в исходном вопросе соответствует коду из исполняемого файла.Приведенный выше код частично отклоняется от исполняемого файла.

Инструкция shlb не имеет смысла для меня.Не удалось найти инструкцию даже в Руководстве разработчика программного обеспечения для архитектуры Intel® 64 и IA-32 .Ближайший матч был зы.

Ответы [ 2 ]

0 голосов
/ 27 июня 2019

Оператор call помещает адрес возврата в стек перед выполнением вызываемой функции.Источник Руководство по разработке программного обеспечения для архитектуры Intel® 64 и IA-32 стр. 225. Другой поток держал недопустимую ссылку на переменную в том же стеке и уменьшал ее, что было сохраненным адресом возврата.В основном поток должен был содержать ссылку на счетчик, подсчитывающий, сколько заданий GenericDevice :: updateValue () все еще ожидают выполнения.По истечении времени ожидания счетчик выйдет из области видимости, но исполняющий поток все еще удерживал недействительную ссылку.Тайм-аут будет происходить редко и только с считывающими устройствами вместо макетов.Таким образом, адрес возврата, сохраненный в стеке, был случайно поврежден.

0 голосов
/ 03 декабря 2018

Как заметил @Jester, другие значения вашего регистра не соответствуют коду, в котором, по вашему мнению, произошел сбой.

Мне удалось получить coredump, когда возникла проблема, и посмотрел код сборки: ... Сегфоут произошел в последней строке фрагмента сборки.

Откуда вы это знаете?Что выводится из where?

Обычно должен быть текущий маркер $RIP, например:

   0x55cd3ef036f4 GenericDevice::updateValue()+92   mov    -0x38(%rbp),%rax   
   0x55cd3ef036f8 GenericDevice::updateValue()+96   mov    (%rax),%rax 
   0x55cd3ef036fb GenericDevice::updateValue()+99   add    $0x40,%rax  
   0x55cd3ef036ff GenericDevice::updateValue()+103  mov    (%rax),%rax 
   0x55cd3ef03702 GenericDevice::updateValue()+106  mov   -0x38(%rbp),%rdx
   0x55cd3ef03706 GenericDevice::updateValue()+110  mov   %rdx,%rdi         
   0x55cd3ef03709 GenericDevice::updateValue()+113  callq  *%rax
=> 0x55cd3ef0370e GenericDevice::updateValue()+118  ....

Вы видите этот маркер?

Если нет, ваш сбой, вероятно, где-то еще (но хорошая работа по анализу ваших данных).

Если вы видите маркер, другие детали, такие как точный производитель процессора и модель, могутвопрос (см., например, это вопрос и ответ).

...