LINUX.ORG.RU

Непонятки с вызовом виртуального метода

 , , ,


0

2

Получил достаточно странный краш на ровном месте:

void DispatchObjectCollsionBox( edict_t *pent )
{
	CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE( pent );
	if( pEntity )
	{
>		pEntity->SetObjectCollisionBox();
	}
	else
		SetObjectCollisionBox( &pent->v );
}

значение pEntity корректно, SetObjectCollizionBox витруальный.
При попытке вызвать метод через gdb получаю такое:
(gdb) print ((CBaseEntity*)pent->pvPrivateData)->SetObjectCollisionBox()
Cannot access memory at address 0xc47521ed

при попытке вызвать метод явно:
(gdb) print ((CBaseEntity*)pent->pvPrivateData)->CBaseEntity::SetObjectCollisionBox()
Cannot access memory at address 0xc47521d5

vtable судя по всему корректен:
(gdb) print ((CBaseEntity*)pent->pvPrivateData)->SetObjectCollisionBox
$15 = {void (CBaseEntity * const)} 0xf706e8f0 <CBaseEntity::SetObjectCollisionBox()>

Что за странные нечитаемые адреса всплывают?

★★★★★

Последнее исправление: mittorn (всего исправлений: 1)

Dump of assembler code for function CBaseEntity::SetObjectCollisionBox():
   0xf706e8f0 <+0>:     call   0xf7056d5e <__x86.get_pc_thunk.dx>
   0xf706e8f5 <+5>:     add    $0x16421f,%edx
   0xf706e8fb <+11>:    mov    0x4(%esp),%eax
   0xf706e8ff <+15>:    mov    0x4(%eax),%ecx
   0xf706e902 <+18>:    cmpl   $0x4,0x10c(%ecx)
   0xf706e909 <+25>:    je     0xf706e9b0 <CBaseEntity::SetObjectCollisionBox()+192>
   0xf706e90f <+31>:    movss  0x10(%ecx),%xmm0
   0xf706e914 <+36>:    movss  0xc(%ecx),%xmm1
   0xf706e919 <+41>:    movss  0x8(%ecx),%xmm2
   0xf706e91e <+46>:    movss  0xe4(%ecx),%xmm4
   0xf706e926 <+54>:    movss  0xe0(%ecx),%xmm5
   0xf706e92e <+62>:    movss  0xdc(%ecx),%xmm6
   0xf706e936 <+70>:    addss  %xmm0,%xmm4
   0xf706e93a <+74>:    addss  %xmm1,%xmm5
   0xf706e93e <+78>:    addss  %xmm2,%xmm6
   0xf706e942 <+82>:    addss  0xf0(%ecx),%xmm0
   0xf706e94a <+90>:    addss  0xec(%ecx),%xmm1
   0xf706e952 <+98>:    addss  0xe8(%ecx),%xmm2
   0xf706e95a <+106>:   movss  -0x82ed4(%edx),%xmm3
   0xf706e962 <+114>:   subss  %xmm3,%xmm6
   0xf706e966 <+118>:   subss  %xmm3,%xmm5
   0xf706e96a <+122>:   subss  %xmm3,%xmm4
   0xf706e96e <+126>:   addss  %xmm3,%xmm2
   0xf706e972 <+130>:   movss  %xmm6,0xc4(%ecx)
   0xf706e97a <+138>:   addss  %xmm3,%xmm1
   0xf706e97e <+142>:   movss  %xmm5,0xc8(%ecx)
   0xf706e986 <+150>:   addss  %xmm3,%xmm0
   0xf706e98a <+154>:   movss  %xmm4,0xcc(%ecx)
   0xf706e992 <+162>:   movss  %xmm2,0xd0(%ecx)
   0xf706e99a <+170>:   movss  %xmm1,0xd4(%ecx)


Dump of assembler code for function DispatchObjectCollsionBox(edict_s*):
   0xf706eaa0 <+0>:     call   0xf7056d5e <__x86.get_pc_thunk.dx>
   0xf706eaa5 <+5>:     add    $0x16406f,%edx
   0xf706eaab <+11>:    push   %ebx
   0xf706eaac <+12>:    mov    0x8(%esp),%eax
   0xf706eab0 <+16>:    test   %eax,%eax
   0xf706eab2 <+18>:    je     0xf706eb88 <DispatchObjectCollsionBox(edict_s*)+232>
   0xf706eab8 <+24>:    mov    0x7c(%eax),%ecx
   0xf706eabb <+27>:    test   %ecx,%ecx
   0xf706eabd <+29>:    je     0xf706eb88 <DispatchObjectCollsionBox(edict_s*)+232>
   0xf706eac3 <+35>:    mov    (%ecx),%ebx
=> 0xf706eac5 <+37>:    mov    0x1c(%ebx),%eax
   0xf706eac8 <+40>:    lea    -0x164224(%edx),%ebx
   0xf706eace <+46>:    cmp    %ebx,%eax
   0xf706ead0 <+48>:    jne    0xf706ee40 <DispatchObjectCollsionBox(edict_s*)+928>
   0xf706ead6 <+54>:    mov    0x4(%ecx),%ecx
   0xf706ead9 <+57>:    cmpl   $0x4,0x10c(%ecx)
   0xf706eae0 <+64>:    je     0xf706ec40 <DispatchObjectCollsionBox(edict_s*)+416>
   0xf706eae6 <+70>:    movss  0x10(%ecx),%xmm0
   0xf706eaeb <+75>:    movss  0xc(%ecx),%xmm1
   0xf706eaf0 <+80>:    movss  0x8(%ecx),%xmm2
   0xf706eaf5 <+85>:    movss  0xe4(%ecx),%xmm7
   0xf706eafd <+93>:    movss  0xe0(%ecx),%xmm5
   0xf706eb05 <+101>:   movss  0xdc(%ecx),%xmm6
   0xf706eb0d <+109>:   addss  %xmm0,%xmm7
   0xf706eb11 <+113>:   addss  %xmm1,%xmm5
   0xf706eb15 <+117>:   addss  %xmm2,%xmm6
   0xf706eb19 <+121>:   addss  0xf0(%ecx),%xmm0
   0xf706eb21 <+129>:   addss  0xec(%ecx),%xmm1
   0xf706eb29 <+137>:   addss  0xe8(%ecx),%xmm2
   0xf706eb31 <+145>:   movss  -0x82ed4(%edx),%xmm4

mittorn ★★★★★
() автор топика
Последнее исправление: mittorn (всего исправлений: 1)
(gdb) info registers
eax            0xf6b5ee1c       -155849188
ecx            0xf8baa4f0       -121985808
edx            0xf71d2b14       -149083372
ebx            0xc47521d1       -998956591
esp            0xfffde288       0xfffde288
ebp            0xfffde2c8       0xfffde2c8
esi            0xf6b5ee1c       -155849188
edi            0xf6b5ee1c       -155849188
eip            0xf706eac5       0xf706eac5 <DispatchObjectCollsionBox(edict_s*)+37>
eflags         0x10286  [ PF SF IF RF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x63     99
mittorn ★★★★★
() автор топика
Ответ на: комментарий от asaw

в данном случае ошибка происходит именно при вызове c++ метода. если бы это был вызов функции в си, такого бы не произошло. А то что это «си с классами» уже десятое дело. c++ тут был нужен только для удобного наследования.

mittorn ★★★★★
() автор топика
Последнее исправление: mittorn (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Этот код писался в 90х под компиляторы его не умеющие. И принято сохранять традиции и кастовать только в c-style.

mittorn ★★★★★
() автор топика
Последнее исправление: mittorn (всего исправлений: 1)
Ответ на: комментарий от mittorn

Тогда продолжайте пользоваться компиляторами 90-ых годов.

peregrine ★★★★★
()
Ответ на: комментарий от no-such-file

работает хотя синтаксисом m_pfnThink = SomeMemberFunction пришлось пожертвовать для gcc4

ЧСХ, при переходе на gcc6 уже ничего не отвалилось

Драма тут:

http://govnokod.ru/19745

http://govnokod.ru/18975

mittorn ★★★★★
() автор топика
Последнее исправление: mittorn (всего исправлений: 2)

Госпади, какой ужас 😵

Stil ★★★★★
()
Ответ на: комментарий от mittorn

ЧСХ, при переходе на gcc6 уже ничего не отвалилось

Ну ок, хозяин - барин.

no-such-file ★★★★★
()
Ответ на: комментарий от a1batross

Этот код успешно компилируется и работает под свежими студиями, и гцц, и шлангами

a = ++a + ++a тоже успешно компилируется и работает.

no-such-file ★★★★★
()
Ответ на: комментарий от asaw

По-моему этот говнокод имеет очень мало общего с C++.

Если послушать фанатов C++, любой код кроме написанного лично ими имеет мало общего с C++.

hateyoufeel ★★★★★
()
Ответ на: комментарий от no-such-file

Это разве что отметает вероятность того, что там окажется другой объект, а не левый адрес. Возвращаемое значение GET_PRIVATE каст менять не будет, а то что находится по адресу — только забота программиста.

a1batross ★★★★★
()
Ответ на: комментарий от no-such-file

Прочитай пост внимательно. То что я делал в gdb не использовало вощвращаемое значение. А GET_PRIVATE ЕМНИП макрос.

mittorn ★★★★★
() автор топика
Ответ на: комментарий от no-such-file

У меня rtti отрублен, какой на### dynamic_cast?
Страусоголовые фанатики собрались

mittorn ★★★★★
() автор топика

Какого типа pent->pvPrivateData, и как этот тип относится к CBaseEntity? И не меняется ли адрес при касте?

Ассемблер лучше смотреть в «cc -S -fverbose-asm».

anonymous
()
Ответ на: комментарий от anonymous

void*. Там может быть тольуо CBaseEntity* или его порождённый класс, причём в случае выше экземпляр корректный

(gdb) print pent->pvPrivateData
$18 = (void *) 0xf8baa4f0
(gdb) print *(CBaseEntity*)pent->pvPrivateData
$19 = {_vptr.CBaseEntity = 0xc47521d1, pev = 0xc579bf80, m_pGoalEnt = 0x4, m_pLink = 0x0, static m_SaveData = {{fieldType = FIELD_CLASSPTR, fieldName = 0xf7151edd "m_pGoalEnt", fieldOffset = 8, fieldSize = 1,
      flags = 0}, {fieldType = FIELD_FUNCTION, fieldName = 0xf7151ee8 "m_pfnThink", fieldOffset = 16, fieldSize = 1, flags = 0}, {fieldType = FIELD_FUNCTION, fieldName = 0xf7151ef3 "m_pfnTouch", fieldOffset = 24,
      fieldSize = 1, flags = 0}, {fieldType = FIELD_FUNCTION, fieldName = 0xf7151efe "m_pfnUse", fieldOffset = 32, fieldSize = 1, flags = 0}, {fieldType = FIELD_FUNCTION, fieldName = 0xf7151f07 "m_pfnBlocked",
      fieldOffset = 40, fieldSize = 1, flags = 0}}, m_pfnThink = (void (CBaseEntity::*)(CBaseEntity * const)) 0xf71303d0 <CBasePlayerItem::AttemptToMaterialize()>, m_pfnTouch = NULL, m_pfnUse = NULL,
  m_pfnBlocked = NULL, ammo_9mm = 0, ammo_357 = 0, ammo_bolts = 0, ammo_buckshot = 0, ammo_rockets = 0, ammo_uranium = 0, ammo_hornets = 0, ammo_argrens = 0, m_flStartThrow = 0, m_flReleaseThrow = 0,
  m_chargeReady = 0, m_fInAttack = 0, m_fireState = 0}

mittorn ★★★★★
() автор топика
Последнее исправление: mittorn (всего исправлений: 1)
Ответ на: комментарий от mittorn

И шта с того, шта у тебя может быть только базовый или порождённый класс?

C-style cast структур и классов годится только в случае, когда у тебя адреса кастуемого и результирующего совпадают. Например:

struct A {
  int a;
  int b;
};

struct B {
  int a;
  int b;
  int c;
};

struct C {
  int d;
  struct A sa;
  struct B sb;
};

struct C sc;
int* i = &sc; //прокатит. Догадываешься, почему?
int* i = (int*)&sc; //тем более
struct A* sa = (struct A*)&sc; //На что будет указывать sa? На sc.sa? Или на sc.d?

То же и с твоим кастованием базовый<->производный в си-стиле. Так шта... Вы работаете с мусором, уважаемый.

alexku
()
Ответ на: комментарий от alexku

Твой пример не описывает случай выше. в производном классе нету никакого мусора в начале. Порождённый класс начинается с базового. Почему так сделано? Как раз чтобы static_cast и c-style-cast работал. И чтобы работал vtable. Ведь адрес экземпляра порождённого класса совпадает с базовым. Без этого было бы невозможно писать без rtti, который в большинстве случаев лишняя обвеса.
Может есть реализации c++ где это не так? Глубоко сомневаюсь в этом.
А теперь собственно ответ на вопрос про левые адреса. См. пост выше. Адрес vtable неправильный. Уже выяснил причину. Экземпляр класса был либо освобождён, либо затёрт по переполнению буфера. т.к я не использовал asan или другие средства глубокой отладки, читать класс я могу (хоть это и UB).
был повреждён страж оставленный аллокатором памяти, но точно я сказать не могу, был ли это Mem_Free (аллокатор кастомный) или что-то поломали извне.

mittorn ★★★★★
() автор топика
Ответ на: комментарий от mittorn

А если у производного класса несколько базовых, с какого из базовых начинается объект производного класса?

iliyap ★★★★★
()
Ответ на: комментарий от iliyap

Не вникал. У меня такого не бывает. В проекте каждый класс имеет лишь один vtable.

mittorn ★★★★★
() автор топика
Ответ на: комментарий от mittorn

Да, пожалуй, мой пример некорректный. Что тут можно сказать? Ищите, кто там Ваш экземпляр класса затирает. Удачи в разгребании говн.

alexku
()
Ответ на: комментарий от alexku

пересоберу с -fsanitize=address, так оно крашнется ещё при попытке попортить данные

mittorn ★★★★★
() автор топика
Ответ на: комментарий от iliyap

По твоему примеру:
http://stackoverflow.com/questions/11603198/virtual-tables-and-memory-layout-...
Разумеется в таких случаях c-style-cast будет некорректен. Думаю, что можно предоложить, что первым будет базовый класс, что позволяет кастовать сразу в него. А дальше static_cast поможет разрулить эту ситуацию. Но тут нужно изучать стандарты и соглашения чтобы так делать.
А если c++ выбран как инструмент для получения наследования, а не идеология, diamond inheritance лучше избежать вообще.
Ведь хорошо когда понимаешь полностью, как работает язык на низком уровне. А в идеологии c++ этого можно и не знать, нужно лишь знать что должно получиться с точки зрения объектов, но не то как они расположены в памяти (т.е полный отказ от old-style-cast, использовать ссылки а не указатели, шаблоны вместо макросов и так далее).

mittorn ★★★★★
() автор топика

(gdb) print ((CBaseEntity*)pent->pvPrivateData)->SetObjectCollisionBox() Cannot access memory at address 0xc47521ed

вместо того что бы вывести значение pent и pent->pvPrivateData вы вызываете функцию, откуда взялся здесь 0xc47521ed, нам нужно угадать ? так это не форум гадалок, в ваших заявляения о том что все корректно нужно усомнится

anonymous
()

Dump of assembler code for function DispatchObjectCollsionBox(edict_s*):

не вся, или половину заинлайнилось, не видно вообще кто там такой +1С но очевидно что из него уже не может прочитать память, она уже невалидная, о чем и говорит дебагер

anonymous
()
Ответ на: комментарий от anonymous

Это vtable. 1C - оффсет функции в нём.

mittorn ★★★★★
() автор топика
Ответ на: комментарий от mittorn

программа падает, дебагер кричит, но вы все проверили и у вас все ок, кто то явно феилит, выберите или вы или софт с дебагером

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.