LINUX.ORG.RU

Обмен с .so'шкой полиморфными объектами и баги компилятора?

 


0

2

Привет. Хочу этого: есть приложение APP, есть модули в виде разделямых либ, которые дописываются в процессе, модули отправляют APP производные классы от базового интерфейсного, и APP с ними однотипно работает. Короче, обычный полиморфизм только между разными elf’ами, значит нельзя исключить личный комплект всяких libstdc++, libc, поэтому либо делать костыль в виде free() торчащий из модулей для удаления полученных APP’ом объектов, либо так (vector не будет перебрасываться между разными elf’ами, конечно же, просто для теста воткнул):

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

struct Animal {
	void (*speak)() = nullptr;
	unique_ptr<Animal> (*copy)(Animal *p) = nullptr;
	void (*print)(Animal *p) = nullptr;
	void (*dstr)(Animal *p) = nullptr;
	bool m_first_destruction = true;
	~Animal() {
		if (m_first_destruction) {
			m_first_destruction = false;
			(*dstr)(this);
		}
	}
};

struct Dog : Animal {
	vector<int> m_v{34, 54, 543};
	static unique_ptr<Animal> copy(Animal *p) {
		Dog *ptr = static_cast<Dog*>(p);
		return unique_ptr<Animal>{new Dog(*ptr)};
	}
	static void print(Animal *p) {
		Dog *ptr = static_cast<Dog*>(p);
		for (int v : ptr->m_v)
			cout << v << endl;
	}
	static void dstr(Animal *p) {
		Dog *ptr = static_cast<Dog*>(p);
		ptr->~Dog();
	}
	Dog() : Animal{[](){cout << "woof\n";},
		&Dog::copy,
		&Dog::print,
		&Dog::dstr} {}
};

struct Cat : Animal {
	vector<int> m_v{2, 4, 1};
	static unique_ptr<Animal> copy(Animal *p) {
		Cat *ptr = static_cast<Cat*>(p);
		return unique_ptr<Animal>{new Cat(*ptr)};
	}
	static void print(Animal *p) {
		Cat *ptr = static_cast<Cat*>(p);
		for (int v : ptr->m_v)
			cout << v << endl;
	}
	static void dstr(Animal *p) {
		Cat *ptr = static_cast<Cat*>(p);
		ptr->~Cat();
	}
	Cat() : Animal{[](){cout << "meow\n";},
		&Cat::copy,
		&Cat::print,
		&Cat::dstr} {}
};

int main() {
	unique_ptr<Animal> cat{new Cat};
	unique_ptr<Animal> cat_cp = cat->copy(cat.get());
	cat_cp->speak();
	cat_cp->print(cat.get());

	unique_ptr<Animal> dog{new Dog};
	unique_ptr<Animal> dog_cp = dog->copy(dog.get());
	dog_cp->speak();
	dog_cp->print(dog.get());
}

т.е. получаю от модуля unique_ptr, делаю его копию через copy(), и дергаю другие «виртуальные» методы. Все вроде норм, в целом устраивает, но есть две проблемки:

  1. Ругается гцц санитайзер на данный код (шланговский нет):
pavlick /tmp $ g++ 3.cc -fsanitize=address
pavlick /tmp $ ./a.out
meow
2
4
1
woof
34
54
543
=================================================================
==10127==ERROR: AddressSanitizer: new-delete-type-mismatch on 0x606000000200 in thread T0:
  object passed to delete has wrong type:
  size of the allocated type:   64 bytes;
  size of the deallocated type: 40 bytes.
    #0 0x7f4977684009 in operator delete(void*, unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cpp:172
    #1 0x558db4b088ae in std::default_delete<Animal>::operator()(Animal*) const (/tmp/a.out+0x48ae)
    #2 0x558db4b07c8a in std::unique_ptr<Animal, std::default_delete<Animal> >::~unique_ptr() (/tmp/a.out+0x3c8a)
    #3 0x558db4b065c3 in main (/tmp/a.out+0x25c3)
    #4 0x7f49770edb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)
    #5 0x558db4b0620d in _start (/tmp/a.out+0x220d)

0x606000000200 is located 0 bytes inside of 64-byte region [0x606000000200,0x606000000240)
allocated by thread T0 here:
    #0 0x7f4977682f41 in operator new(unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cpp:99
    #1 0x558db4b068e9 in Dog::copy(Animal*) (/tmp/a.out+0x28e9)
    #2 0x558db4b06543 in main (/tmp/a.out+0x2543)
    #3 0x7f49770edb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)

SUMMARY: AddressSanitizer: new-delete-type-mismatch /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cpp:172 in operator delete(void*, unsigned long)
==10127==HINT: if you don't care about these errors you may set ASAN_OPTIONS=new_delete_type_mismatch=0
==10127==ABORTING

Я туплю или надо отправить багрепорт? Я так понимаю, что дело хитрого хака внутри деструктор Animal() он не понимает, видимо, что все деструкторы отрабатывают нормально.

  1. Ну и не очень нравится вот это:
unique_ptr<Animal> cat_cp = cat->copy(cat.get());

т.е. мне приходится дважды передавать указатель, что излишне, достаточно однажды это делать. Либо обмазывать макросами, либо закостылить что такое:

struct Base {
	void (Base::*fp)() = nullptr;
};
struct Derived : Base {
	void fd();
	Derived(): Base{&fd} {}
};

чтобы потом дергать как-то так без дублирования (baseptr->*fp)(); Но я пока не могу сообразить как. Какие-нибудь идеи?

значит нельзя исключить личный комплект всяких libstdc++, libc

В Линуксе можно. В одном процессе обычно только одна рантайм библиотека. Заморачиваться с поддержкой множества рантайм библиотек не вижу смысла.

Просто пользуйтесь классом из другой динамической библиотеки и не заморачивайтесь. Это не Windows с over 9000 msvcr*.dll.

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

До меня только дошло, что в этой виртуальности из палок нет смысла. Тот же эффект, когда виртуальные методы производных классов inline, а дальше тупо дергаю виртуальную copy() в APP. Ок, а если несколько эльфов скомпилины с подинклуженной инлайн функцией, то каждый будет дергать свой экземпляр или один на всех может быть при каких-то условиях? Под отладчиком посмотрю чуть позже, но вопросы все равно могут остаться - например, подключать не через dlopen, а передавать либу линкеру, хз как он там решит (ну я не знаю во всяком случае).

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

Да:

pavlick /tmp $ cat 1.cc
#include <iostream>
using namespace std;

inline void f()
{
        cout << "hello world\n";
}

void a() {f();}
pavlick /tmp $ cat 2.cc
#include <iostream>
using namespace std;
inline void f()
{
        cout << "bye world\n";
}
void a();
int main()
{
        f();
        a();
}
pavlick /tmp $ g++ 1.cc -shared -fpic -o d.so
pavlick /tmp $ g++ 2.cc /tmp/d.so
pavlick /tmp $ ./a.out
bye world
bye world

Можно там экспортом управлять, длсим() дергать, конечно.

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

ок, понял. В целом я загоняюсь, конечно. Просто если захотеть, то таки можно слинковать разные модули со своими либами вкупе с загрузкой через dlopen() и не передав при линке исполняемого файла -rdynamic, в теории должны дергаться разные функции. Видимо для каких-то железобетонных гарантий надо все же вытаскивать из подгружаемый модулей free() костыль.

pavlick ★★ ()

Я туплю или надо отправить багрепорт?

Если ты делаешь unique_ptr<Animal> указывать на объект унаследованного класса, Animal должен иметь виртуальный деструктор. Либо используй корректно инициализированный unique_ptr<Animal, some_deleter>, который будет правильно удалять.

anonymous ()

Я так понимаю, что дело хитрого хака внутри деструктор Animal() он не понимает, видимо, что все деструкторы отрабатывают нормально.

Санитайзер должен понимать хаки в коде с UB?

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

По поводу подгрузки нескольких libc. Конечно, надуманный пример, более того - выдается воринг, но все же возможно:

pavlick /tmp $ cat 1.c
#include <stdio.h>
#include <stdlib.h>
void *f()
{
        printf("qqqqqqqqqqqqqqqqqq");
        void *p = malloc(20);
        free(p);
        return malloc(20);
}

pavlick /tmp $ cat 2.c
#include <dlfcn.h>
#include <stdlib.h>

int main()
{
        void *handle = dlopen("./d.so", RTLD_LAZY);
        if (!handle)
                abort();
        void*(*f)() = (void*(*)()) dlsym(handle, "f");
        if (f == NULL)
                abort();

        void *p = malloc(20);
        free(p);
        p = malloc(20);
        free(p);
        f();
}
pavlick /tmp $ gcc 1.c -shared -fpic -g -o d.so
pavlick /tmp $ gcc -static -g 2.c -ldl
/usr/bin/ld: /tmp/ccvOu3Kq.o: in function `main':
/tmp/2.c:6: warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

В итоге в приложении живут две libc, дергаются два разны malloc’а из so и исполняемого файла.

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

Давай без пустых вбросов, где конкретно?

Из Обмен с .so'шкой полиморфными объектами и баги компилятора? (комментарий) недостаточно ясно?

struct Animal {};
struct Dog : Animal {};

Animal* p = new Dog{};
delete p; // UB

Я говорю - нет там ЮБ.

Это какое-то заклинание? Сколько раз нужно сказать «нет UB» про код в котором UB чтобы UB исчезло?

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

Да, согласен, ЮБ. Думал, что деструкторы вызвал - норм.

Либо используй корректно инициализированный unique_ptr<Animal, some_deleter>, который будет правильно удалять.

Более того - именно так надо решать изначальную задачу, наверное. В so цеплять deleter сошным delete внутри и возращать приложению, никаких free() из модуля болтаться не будет. А поделка из первого сообщения не поможет.

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

Заморачиваться с поддержкой множества рантайм библиотек не вижу смысла.

Пришел к тому же выводу. Как бы мы не компилировали сошку, все внутри неё пойдут через PLT таблицу, значит сошка должна связаться с стд либами из исполняемого файла, которые будут в линк мэпе раньше. Если линкуем исполняемы файл статически и передаём либу через командрую строку, то тоже норм, а если пытаемся делать dlopen(), то получаем предупреждения уже на стадии компиляции.

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

pavlick ★★ ()