[Assembler, C, C++] Девиртуализация в последних версиях gcc и clang
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Что это вообще такоеДевиртуализация (devirtualization) — оптимизация виртуальных функций. Если компилятор точно знает тип объекта, он может вызывать его виртуальные функции напрямую, не используя таблицу виртуальных функций.
В этой статье мы проверим насколько хорошо с этой задачей справляются компиляторы gcc и clang.
ТестированиеВсе тесты производились на Arch Linux x86-64. Использовались gcc 4.8.2 и clang 3.3.
вывод gcc -v
SPL
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-unknown-linux-gnu/4.8.2/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with: /build/gcc-multilib/src/gcc-4.8.2/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-cloog-backend=isl --disable-cloog-version-check --enable-lto --enable-plugin --with-linker-hash-style=gnu --enable-multilib --disable-werror --enable-checking=release
Thread model: posix
gcc version 4.8.2 (GCC)
вывод clang -v
SPL
clang version 3.3 (tags/RELEASE_33/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
Чтобы было проще разбираться в дизассемблированном коде, использовался флаг -nostartfiles. Если его указать, то компилятор не будет генерировать код, вызывающий функцию main с нужными параметрами. Функция, которая получает управление первой, называется _start.
В коде, который мы будем компилировать, содержится два класса:
- класс A — абстрактный класс с трёмя методами: increment(), decrement() и get()
class A {
public:
virtual ~A() {
}
virtual void increment() = 0;
virtual void decrement() = 0;
virtual int get() = 0;
};
- класс B — класс наследующийся от А и реализующий все абстрактные методы
class B : public A {
public:
B() : x(0) {
}
virtual void increment() {
x++;
}
virtual void decrement() {
x--;
}
virtual int get() {
return x;
}
private:
int x;
};
Версия 1Всё в одном файле.
код
SPL
class A {
public:
virtual ~A() {
}
virtual void increment() = 0;
virtual void decrement() = 0;
virtual int get() = 0;
};
class B : public A {
public:
B() : x(0) {
}
virtual void increment() {
x++;
}
virtual void decrement() {
x--;
}
virtual int get() {
return x;
}
private:
int x;
};
extern "C" {
int printf(const char * format, ...);
void exit(int status);
void _start() {
B b;
b.increment();
b.increment();
b.decrement();
printf("%d\n", b.get());
exit(0);
}
}
Результат: gcc с флагами -O1, -O2, -O3, -Os и clang с флагами -O2, -O3, -Os произвели девиртуализацию и поняли, что второй аргумент функции printf всегда равен 1. Код, сгенерированный с помощью gcc -O1:
<_start>:
sub rsp,0x8
; вызов printf
mov esi,0x1 ; записываем значение b.get() в ESI
mov edi,0x4003a2 ; записываем адрес строки "%s\n" в EDI
mov eax,0x0
call 400360 <printf@plt> ; вызываем printf
; вызов exit
mov edi,0x0 ; записываем код ошибки в регистр EDI
call 400370 <exit@plt> ; вызываем exit
Версия 2Всё в одном файле, вызываем виртуальные методы через указатель на базовый класс
код
SPL
class A {
public:
virtual ~A() {
}
virtual void increment() = 0;
virtual void decrement() = 0;
virtual int get() = 0;
};
class B : public A {
public:
B() : x(0) {
}
virtual void increment() {
x++;
}
virtual void decrement() {
x--;
}
virtual int get() {
return x;
}
private:
int x;
};
extern "C" {
int printf(const char * format, ...);
void exit(int status);
void _start() {
A * a = new B;
a->increment();
a->increment();
a->decrement();
printf("%d\n", a->get());
exit(0);
}
}
Результат: clang с флагами -O2, -O3, -Os генерирует такой же код, что и в варианте 1. gcc ведёт себя странно: с флагами -O1, -O2, -O3, -Os он генерирует такой код:
<_start>:
push rbx
; выделение памяти
mov edi,0x10 ; кол-во байт (16)
call 400560 <_Znwm@plt> ; вызываем функцию, выделяющую память (возвращает указатель в RAX)
mov rbx,rax ; сохраняем указатель на экземпляр класса в RBX
; конструктор
mov QWORD PTR [rax],0x4006d0 ; инициализируем таблицу виртуальных функций
mov DWORD PTR [rax+0x8],0x1 ; инициализируем поле x единицей (первый вызов increment заинлайнился)
; второй вызов increment
mov rdi,rax ; записываем указатель на экземпляр класса в RDI
call 4005ca <_ZN1B9incrementEv> ; вызываем increment
; вызов decrement
mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX
mov rdi,rbx ; записываем указатель на экземпляр класса в RDI
call QWORD PTR [rax+0x18] ; вызываем decrement через таблицу виртуальных функций
; вызов get
mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX
mov rdi,rbx ; записываем указатель на экземпляр класса в RDI
call QWORD PTR [rax+0x20] ; вызываем get через таблицу виртуальных функций (результат в EAX)
; вызов printf
mov esi,eax ; записываем значение b.get() в ESI
mov edi,0x400620 ; записываем адрес строки "%s\n" в EDI
mov eax,0x0
call 400520 <printf@plt> ; вызываем printf
; вызов exit
mov edi,0x0 ; записываем код ошибки в регистр EDI
call 400370 <exit@plt> ; вызываем exit
Версия 3Для каждого класса отдельный .hpp и .cpp файл
код
SPL
a.hpp
#pragma once
class A {
public:
virtual ~A();
virtual void increment() = 0;
virtual void decrement() = 0;
virtual int get() = 0;
};
a.cpp
#include "a.hpp"
A::~A() {
}
b.hpp
#pragma once
#include "a.hpp"
class B : public A {
public:
B();
virtual void increment();
virtual void decrement();
virtual int get();
private:
int x;
};
b.cpp
#include "b.hpp"
B::B() : x(0) {
}
void B::increment() {
x++;
}
void B::decrement() {
x--;
}
int B::get() {
return x;
}
test.cpp
#include "b.hpp"
extern "C" {
int printf(const char * format, ...);
void exit(int status);
void _start() {
B b;
b.increment();
b.increment();
b.decrement();
printf("%d\n", b.get());
exit(0);
}
}
Результат: оба компилятора успешно девиртуализировали все функции, но не смогли их заинлайнить, так как они находятся в разных единицах трансляции:
<_start>:
push rbx
sub rsp,0x10 ; выделяем пямять на стеке
; вызов конструктора
lea rbx,[rsp] ; сохраняем указатель на экземпляр класса в RBX
mov rdi,rbx ; записываем указатель на экземпляр класса в RDI
call 400720 <_ZN1BC1Ev> ; вызываем конструктор
; вызов increment
mov rdi,rbx ; записываем указатель на экземпляр класса в RDI
call 400740 <_ZN1B9incrementEv> ; вызываем increment
; вызов increment
lea rdi,[rsp] ; записываем указатель на экземпляр класса в RDI
call 400740 <_ZN1B9incrementEv> ; вызываем increment
; вызов decrement
lea rdi,[rsp] ; записываем указатель на экземпляр класса в RDI
call 400750 <_ZN1B9decrementEv> ; вызываем decrement
; вызов get
lea rdi,[rsp] ; записываем указатель на экземпляр класса в RDI
call 400760 <_ZN1B3getEv> ; вызываем get
; вызов printf
mov edi,0x400820 ; записываем адрес строки "%s\n" в EDI
mov esi,eax ; записываем значение b.get() в ESI
xor al,al
call 4005d0 <printf@plt> ; вызываем printf
; вызов exit
xor edi,edi ; записываем код ошибки в регистр EDI
call 4005e0 <exit@plt> ; вызываем exit
Версия 4Для каждого класса отдельный .hpp и .cpp файл, LTO (Link Time Optimization, она же Interprocedural optimization, флаг -flto)
Код тот же, что и в предыдущем примере
Результат: clang девиртуализировал и заинлайнил все методы (ассемблерный код как в примере 1), gcc по какой-то причине заинлайнил всё кроме конструктора:
<_start>:
push rbx
sub rsp,0x10 ; выделяем пямять на стеке
; вызов конструктора
mov rdi,rsp ; записываем указатель на экземпляр класса в регистр RDI
call 400660 <_ZN1BC1Ev.2444> ; вызываем конструктор
; вычисление значения поля x
mov eax,DWORD PTR [rsp+0x8] ; загружаем старое значение поля x (0)
lea esi,[rax+0x1] ; увеличиваем его не 1
mov DWORD PTR [rsp+0x8],esi ; записываем результат
; вызов printf
mov edi,0x400700 ; записываем адрес строки "%s\n" в EDI
mov eax,0x0 ; записываем значение b.get() в ESI
call 4005f0 <printf@plt> ; вызываем printf
; вызов exit
mov edi,0x0 ; записываем код ошибки в регистр EDI
call 400620 <exit@plt> ; вызываем exit
Версия 5Для каждого класса отдельный .hpp и .cpp файл, LTO, вызываем виртуальные методы через указатель на базовый класс
код
SPL
a.hpp
#pragma once
class A {
public:
virtual ~A();
virtual void increment() = 0;
virtual void decrement() = 0;
virtual int get() = 0;
};
a.cpp
#include "a.hpp"
A::~A() {
}
b.hpp
#pragma once
#include "a.hpp"
class B : public A {
public:
B();
virtual void increment();
virtual void decrement();
virtual int get();
private:
int x;
};
b.cpp
#include "b.hpp"
B::B() : x(0) {
}
void B::increment() {
x++;
}
void B::decrement() {
x--;
}
int B::get() {
return x;
}
test.cpp
#include "b.hpp"
extern "C" {
int printf(const char * format, ...);
void exit(int status);
void _start() {
A * a = new B;
a->increment();
a->increment();
a->decrement();
printf("%d\n", a->get());
exit(0);
}
}
Результат: и gcc, и clang смогли девиртуализировать только первый вызов increment:
<_start>:
push rbx
; выделение памяти
mov edi,0x10 ; кол-во байт (16)
call 400480 <_Znwm@plt> ; вызываем функцию, выделяющую память (возвращает указатель в RAX)
mov rbx,rax ; сохраняем указатель на экземпляр класса в RBX
; конструктор
mov QWORD PTR [rbx],0x4005b0 ; инициализируем таблицу виртуальных функций
mov DWORD PTR [rbx+0x8],0x0 ; инициализируем поле x
; первый вызов increment
mov rdi,rbx ; записываем указатель на экземпляр класса в RDI
call 400520 <_ZN1B9incrementEv> ; вызываем increment
; второй вызов increment
mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX
mov rdi,rbx ; записываем указатель на экземпляр класса в RDI
call QWORD PTR [rax+0x10] ; вызываем increment
; вызов decrement
mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX
mov rdi,rbx ; записываем указатель на экземпляр класса в RDI
call QWORD PTR [rax+0x18] ; вызываем decrement
; вызов get
mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX
mov rdi,rbx ; записываем указатель на экземпляр класса в RDI
call QWORD PTR [rax+0x20] ; вызываем get
; вызов printf
mov edi,0x400570 ; записываем адрес строки "%s\n" в EDI
mov esi,eax ; записываем значение b.get() в ESI
xor al,al
call 400490 <printf@plt> ; вызываем printf
; вызов exit
xor edi,edi ; записываем код ошибки в регистр EDI
pop rbx
jmp 4004a0 <exit@plt> ; вызываем exit
Выводы
- Наилучший результат достигается когда все классы в одной единице трансляции
- Во всех тестах результаты clang не хуже или лучше результатов gcc
Исходники: github.com/alkedr/devirtualize-test
===========
Источник:
habr.com
===========
Похожие новости:
- [Информационная безопасность] Взломаны известные аккаунты твиттера, злоумышленники предлагают перевести деньги на биткоин-адрес
- [SQL, Visual Basic for Applications] in2sql: Работаем с разнообразием ODBC источников
- Из базовой поставки Ubuntu будет удалён компонент для отправки сведений о пакетах
- [Клиентская оптимизация, Поисковая оптимизация, Разработка веб-сайтов] Проверка разметки сайтов-участников W3C
- [Интервью, История IT, Старое железо] Владимир Китов: «Сначала думал, что не смогу в капиталистической системе работать
- [JavaScript, Python, Программирование, Разработка веб-сайтов] Надоел JavaScript — используй браузерный Python (перевод)
- [Биотехнологии, Здоровье, Лазеры] How to “sew up” the retina and should it be done? (перевод)
- Релиз Chrome 84
- [Open source, OpenStreetMap, Визуализация данных, Научно-популярное, Программирование] Делаем маршрутизацию (роутинг) на OpenStreetMap. Введение
- [Производство и разработка электроники, Процессоры] Apple Silicon: конец эры Wintel (перевод)
Теги для поиска: #_assembler, #_c, #_c++, #_assembler, #_c, #_c++, #_gcc, #_clang, #_virtualnye_funktsii (виртуальные функции), #_devirtualizatsija (девиртуализация), #_assembler, #_c, #_c++
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:35
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Что это вообще такоеДевиртуализация (devirtualization) — оптимизация виртуальных функций. Если компилятор точно знает тип объекта, он может вызывать его виртуальные функции напрямую, не используя таблицу виртуальных функций. В этой статье мы проверим насколько хорошо с этой задачей справляются компиляторы gcc и clang. ТестированиеВсе тесты производились на Arch Linux x86-64. Использовались gcc 4.8.2 и clang 3.3. вывод gcc -vSPLUsing built-in specs.
COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-unknown-linux-gnu/4.8.2/lto-wrapper Target: x86_64-unknown-linux-gnu Configured with: /build/gcc-multilib/src/gcc-4.8.2/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-cloog-backend=isl --disable-cloog-version-check --enable-lto --enable-plugin --with-linker-hash-style=gnu --enable-multilib --disable-werror --enable-checking=release Thread model: posix gcc version 4.8.2 (GCC) вывод clang -vSPLclang version 3.3 (tags/RELEASE_33/final)
Target: x86_64-unknown-linux-gnu Thread model: posix Чтобы было проще разбираться в дизассемблированном коде, использовался флаг -nostartfiles. Если его указать, то компилятор не будет генерировать код, вызывающий функцию main с нужными параметрами. Функция, которая получает управление первой, называется _start. В коде, который мы будем компилировать, содержится два класса:
Версия 1Всё в одном файле. кодSPLclass A {
public: virtual ~A() { } virtual void increment() = 0; virtual void decrement() = 0; virtual int get() = 0; }; class B : public A { public: B() : x(0) { } virtual void increment() { x++; } virtual void decrement() { x--; } virtual int get() { return x; } private: int x; }; extern "C" { int printf(const char * format, ...); void exit(int status); void _start() { B b; b.increment(); b.increment(); b.decrement(); printf("%d\n", b.get()); exit(0); } } Результат: gcc с флагами -O1, -O2, -O3, -Os и clang с флагами -O2, -O3, -Os произвели девиртуализацию и поняли, что второй аргумент функции printf всегда равен 1. Код, сгенерированный с помощью gcc -O1: <_start>:
sub rsp,0x8 ; вызов printf mov esi,0x1 ; записываем значение b.get() в ESI mov edi,0x4003a2 ; записываем адрес строки "%s\n" в EDI mov eax,0x0 call 400360 <printf@plt> ; вызываем printf ; вызов exit mov edi,0x0 ; записываем код ошибки в регистр EDI call 400370 <exit@plt> ; вызываем exit Версия 2Всё в одном файле, вызываем виртуальные методы через указатель на базовый класс кодSPLclass A {
public: virtual ~A() { } virtual void increment() = 0; virtual void decrement() = 0; virtual int get() = 0; }; class B : public A { public: B() : x(0) { } virtual void increment() { x++; } virtual void decrement() { x--; } virtual int get() { return x; } private: int x; }; extern "C" { int printf(const char * format, ...); void exit(int status); void _start() { A * a = new B; a->increment(); a->increment(); a->decrement(); printf("%d\n", a->get()); exit(0); } } Результат: clang с флагами -O2, -O3, -Os генерирует такой же код, что и в варианте 1. gcc ведёт себя странно: с флагами -O1, -O2, -O3, -Os он генерирует такой код: <_start>:
push rbx ; выделение памяти mov edi,0x10 ; кол-во байт (16) call 400560 <_Znwm@plt> ; вызываем функцию, выделяющую память (возвращает указатель в RAX) mov rbx,rax ; сохраняем указатель на экземпляр класса в RBX ; конструктор mov QWORD PTR [rax],0x4006d0 ; инициализируем таблицу виртуальных функций mov DWORD PTR [rax+0x8],0x1 ; инициализируем поле x единицей (первый вызов increment заинлайнился) ; второй вызов increment mov rdi,rax ; записываем указатель на экземпляр класса в RDI call 4005ca <_ZN1B9incrementEv> ; вызываем increment ; вызов decrement mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX mov rdi,rbx ; записываем указатель на экземпляр класса в RDI call QWORD PTR [rax+0x18] ; вызываем decrement через таблицу виртуальных функций ; вызов get mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX mov rdi,rbx ; записываем указатель на экземпляр класса в RDI call QWORD PTR [rax+0x20] ; вызываем get через таблицу виртуальных функций (результат в EAX) ; вызов printf mov esi,eax ; записываем значение b.get() в ESI mov edi,0x400620 ; записываем адрес строки "%s\n" в EDI mov eax,0x0 call 400520 <printf@plt> ; вызываем printf ; вызов exit mov edi,0x0 ; записываем код ошибки в регистр EDI call 400370 <exit@plt> ; вызываем exit Версия 3Для каждого класса отдельный .hpp и .cpp файл кодSPLa.hpp
#pragma once
class A { public: virtual ~A(); virtual void increment() = 0; virtual void decrement() = 0; virtual int get() = 0; }; a.cpp #include "a.hpp"
A::~A() { } b.hpp #pragma once
#include "a.hpp" class B : public A { public: B(); virtual void increment(); virtual void decrement(); virtual int get(); private: int x; }; b.cpp #include "b.hpp"
B::B() : x(0) { } void B::increment() { x++; } void B::decrement() { x--; } int B::get() { return x; } test.cpp #include "b.hpp"
extern "C" { int printf(const char * format, ...); void exit(int status); void _start() { B b; b.increment(); b.increment(); b.decrement(); printf("%d\n", b.get()); exit(0); } } Результат: оба компилятора успешно девиртуализировали все функции, но не смогли их заинлайнить, так как они находятся в разных единицах трансляции: <_start>:
push rbx sub rsp,0x10 ; выделяем пямять на стеке ; вызов конструктора lea rbx,[rsp] ; сохраняем указатель на экземпляр класса в RBX mov rdi,rbx ; записываем указатель на экземпляр класса в RDI call 400720 <_ZN1BC1Ev> ; вызываем конструктор ; вызов increment mov rdi,rbx ; записываем указатель на экземпляр класса в RDI call 400740 <_ZN1B9incrementEv> ; вызываем increment ; вызов increment lea rdi,[rsp] ; записываем указатель на экземпляр класса в RDI call 400740 <_ZN1B9incrementEv> ; вызываем increment ; вызов decrement lea rdi,[rsp] ; записываем указатель на экземпляр класса в RDI call 400750 <_ZN1B9decrementEv> ; вызываем decrement ; вызов get lea rdi,[rsp] ; записываем указатель на экземпляр класса в RDI call 400760 <_ZN1B3getEv> ; вызываем get ; вызов printf mov edi,0x400820 ; записываем адрес строки "%s\n" в EDI mov esi,eax ; записываем значение b.get() в ESI xor al,al call 4005d0 <printf@plt> ; вызываем printf ; вызов exit xor edi,edi ; записываем код ошибки в регистр EDI call 4005e0 <exit@plt> ; вызываем exit Версия 4Для каждого класса отдельный .hpp и .cpp файл, LTO (Link Time Optimization, она же Interprocedural optimization, флаг -flto) Код тот же, что и в предыдущем примере Результат: clang девиртуализировал и заинлайнил все методы (ассемблерный код как в примере 1), gcc по какой-то причине заинлайнил всё кроме конструктора: <_start>:
push rbx sub rsp,0x10 ; выделяем пямять на стеке ; вызов конструктора mov rdi,rsp ; записываем указатель на экземпляр класса в регистр RDI call 400660 <_ZN1BC1Ev.2444> ; вызываем конструктор ; вычисление значения поля x mov eax,DWORD PTR [rsp+0x8] ; загружаем старое значение поля x (0) lea esi,[rax+0x1] ; увеличиваем его не 1 mov DWORD PTR [rsp+0x8],esi ; записываем результат ; вызов printf mov edi,0x400700 ; записываем адрес строки "%s\n" в EDI mov eax,0x0 ; записываем значение b.get() в ESI call 4005f0 <printf@plt> ; вызываем printf ; вызов exit mov edi,0x0 ; записываем код ошибки в регистр EDI call 400620 <exit@plt> ; вызываем exit Версия 5Для каждого класса отдельный .hpp и .cpp файл, LTO, вызываем виртуальные методы через указатель на базовый класс кодSPLa.hpp
#pragma once
class A { public: virtual ~A(); virtual void increment() = 0; virtual void decrement() = 0; virtual int get() = 0; }; a.cpp #include "a.hpp"
A::~A() { } b.hpp #pragma once
#include "a.hpp" class B : public A { public: B(); virtual void increment(); virtual void decrement(); virtual int get(); private: int x; }; b.cpp #include "b.hpp"
B::B() : x(0) { } void B::increment() { x++; } void B::decrement() { x--; } int B::get() { return x; } test.cpp #include "b.hpp"
extern "C" { int printf(const char * format, ...); void exit(int status); void _start() { A * a = new B; a->increment(); a->increment(); a->decrement(); printf("%d\n", a->get()); exit(0); } } Результат: и gcc, и clang смогли девиртуализировать только первый вызов increment: <_start>:
push rbx ; выделение памяти mov edi,0x10 ; кол-во байт (16) call 400480 <_Znwm@plt> ; вызываем функцию, выделяющую память (возвращает указатель в RAX) mov rbx,rax ; сохраняем указатель на экземпляр класса в RBX ; конструктор mov QWORD PTR [rbx],0x4005b0 ; инициализируем таблицу виртуальных функций mov DWORD PTR [rbx+0x8],0x0 ; инициализируем поле x ; первый вызов increment mov rdi,rbx ; записываем указатель на экземпляр класса в RDI call 400520 <_ZN1B9incrementEv> ; вызываем increment ; второй вызов increment mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX mov rdi,rbx ; записываем указатель на экземпляр класса в RDI call QWORD PTR [rax+0x10] ; вызываем increment ; вызов decrement mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX mov rdi,rbx ; записываем указатель на экземпляр класса в RDI call QWORD PTR [rax+0x18] ; вызываем decrement ; вызов get mov rax,QWORD PTR [rbx] ; записываем указатель на таблицу виртуальных функций в RAX mov rdi,rbx ; записываем указатель на экземпляр класса в RDI call QWORD PTR [rax+0x20] ; вызываем get ; вызов printf mov edi,0x400570 ; записываем адрес строки "%s\n" в EDI mov esi,eax ; записываем значение b.get() в ESI xor al,al call 400490 <printf@plt> ; вызываем printf ; вызов exit xor edi,edi ; записываем код ошибки в регистр EDI pop rbx jmp 4004a0 <exit@plt> ; вызываем exit Выводы
Исходники: github.com/alkedr/devirtualize-test =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:35
Часовой пояс: UTC + 5