LINUX.ORG.RU

Расширенная отладка кода в gcc

 , ,


3

3

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

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

Ответ на: комментарий от SoulThreads

Спасибо, то что надо, многое нашлось. Знал что есть подобное, просто слов подобрать не мог. Теперь название ясно - address sanitizer и далее вперед! :)

https://github.com/google/sanitizers/wiki/AddressSanitizer например

I-Love-Microsoft ★★★★★
() автор топика
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)

crash.cpp

#if(!defined __linux__ && !defined __MINGW32__)
#include <windows.h>
#include <winnt.h>

#include <string>
#include <vector>
#include <Psapi.h>
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <iterator>
#include <fstream>

#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "dbghelp.lib")

// Some versions of imagehlp.dll lack the proper packing directives themselves
// so we need to do it.
#pragma pack( push, before_imagehlp, 8 )
#include <imagehlp.h>
#pragma pack( pop, before_imagehlp )

struct module_data {
  std::string image_name;
  std::string module_name;
  void *base_address;
  uint32_t load_size;
};
typedef std::vector<module_data> ModuleList;

HANDLE thread_ready;

bool show_stack(std::ostream &, HANDLE hThread, CONTEXT& c);
LONG WINAPI myUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *ep);
//uint32_t Filter( EXCEPTION_POINTERS *ep );
void *load_modules_symbols( HANDLE hProcess, uint32_t pid );

// if you use C++ exception handling: install a translator function
// with set_se_translator(). the context of that function (but *not*
// afterwards), you can either do your stack dump, or save the CONTEXT
// record as a local copy. Note that you must do the stack dump at the
// earliest opportunity, to avoid the interesting stack-frames being gone
// by the time you do the dump.
LONG WINAPI myUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *ep) {
  HANDLE thread;

  DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
    GetCurrentProcess(), &thread, 0, false, DUPLICATE_SAME_ACCESS);
  //std::cout << "Walking stack.";
	std::ofstream ofs;
	ofs.open("crash_dump.txt");
  show_stack(ofs, thread, *(ep->ContextRecord));
	ofs.close();
  //std::cout << "\nEnd of stack walk.\n";
  CloseHandle(thread);

  return EXCEPTION_EXECUTE_HANDLER;
}

class SymHandler {
  HANDLE p;
public:
  SymHandler(HANDLE process, char const *path=nullptr, bool intrude = false) : p(process) { 
    if(!SymInitialize(p, path, intrude)) 
      throw(std::logic_error("Unable to initialize symbol handler"));
  }
  ~SymHandler() { SymCleanup(p); }
};

#ifdef _M_X64
STACKFRAME64 init_stack_frame(CONTEXT c) {
  STACKFRAME64 s;
  s.AddrPC.Offset = c.Rip;
  s.AddrPC.Mode = AddrModeFlat;
  s.AddrStack.Offset = c.Rsp;
  s.AddrStack.Mode = AddrModeFlat;  
  s.AddrFrame.Offset = c.Rbp;
  s.AddrFrame.Mode = AddrModeFlat;
  return s;
}
#else
STACKFRAME init_stack_frame(CONTEXT c) {
  STACKFRAME s;
  s.AddrPC.Offset = c.Eip;
  s.AddrPC.Mode = AddrModeFlat;
  s.AddrStack.Offset = c.Esp;
  s.AddrStack.Mode = AddrModeFlat;  
  s.AddrFrame.Offset = c.Ebp;
  s.AddrFrame.Mode = AddrModeFlat;
  return s;
}
#endif

void sym_options(uint32_t add, uint32_t remove=0) {
  uint32_t symOptions = SymGetOptions();
  symOptions |= add;
  symOptions &= ~remove;
  SymSetOptions(symOptions);
}

class symbol { 
#ifdef _M_X64
	typedef IMAGEHLP_SYMBOL64 sym_type;
#else
  typedef IMAGEHLP_SYMBOL sym_type;
#endif
  sym_type *sym;
  static const int max_name_len = 1024;
public:
#ifdef _M_X64
	symbol(HANDLE process, DWORD64 address) : sym((sym_type *)::operator new(sizeof(*sym) + max_name_len)) {
#else
  symbol(HANDLE process, uint32_t address) : sym((sym_type *)::operator new(sizeof(*sym) + max_name_len)) {
#endif
    memset(sym, '\0', sizeof(*sym) + max_name_len);
    sym->SizeOfStruct = sizeof(*sym);
    sym->MaxNameLength = max_name_len;
#ifdef _M_X64
    DWORD64 displacement;

		if(!SymGetSymFromAddr64(process, address, &displacement, sym))
#else
        uint32_t displacement;

		if(!SymGetSymFromAddr(process, address, &displacement, sym))
#endif
      throw(std::logic_error("Bad symbol"));
  }

  std::string name() { return std::string(sym->Name); }
  std::string undecorated_name() { 
    std::vector<char> und_name(max_name_len);
    UnDecorateSymbolName(sym->Name, &und_name[0], max_name_len, UNDNAME_COMPLETE);
    return std::string(&und_name[0], strlen(&und_name[0]));
  }
};

bool show_stack(std::ostream &os, HANDLE hThread, CONTEXT& c) {
  HANDLE process = GetCurrentProcess();
  int frame_number=0;
  unsigned long offset_from_symbol=0;
#ifdef _M_X64
  IMAGEHLP_LINE64 line = {0};
	IMAGEHLP_MODULE64 mod = {0,};
#else
	IMAGEHLP_LINE line = {0};
	IMAGEHLP_MODULE mod = {0,};
#endif

  SymHandler handler(process);

  sym_options(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);

  void *base = load_modules_symbols(process, GetCurrentProcessId());

#ifdef _M_X64
  STACKFRAME64 s = init_stack_frame(c);
#else
	STACKFRAME s = init_stack_frame(c);
#endif

  line.SizeOfStruct = sizeof line;

  IMAGE_NT_HEADERS *h = ImageNtHeader(base);
  uint32_t image_type = h->FileHeader.Machine;

  do {
#ifdef _M_X64
    if(!StackWalk64(image_type, process, hThread, &s, &c, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
#else
		if(!StackWalk(image_type, process, hThread, &s, &c, nullptr, SymFunctionTableAccess, SymGetModuleBase, nullptr))
#endif
      return false;

    os << std::setw(3) << "\n" << frame_number << "\t";
		//os << hThread << "\t";
    if( s.AddrPC.Offset != 0 ) {
      os << symbol(process, s.AddrPC.Offset).undecorated_name();

#ifdef _M_X64
			if(SymGetModuleInfo64( process, s.AddrPC.Offset, &mod ) ) 
#else
			if(SymGetModuleInfo( process, s.AddrPC.Offset, &mod )) 
#endif
				os << "\t" << mod.ModuleName << "\t";

#ifdef _M_X64
      if(SymGetLineFromAddr64( process, s.AddrPC.Offset, &offset_from_symbol, &line ) ) 
#else
			if(SymGetLineFromAddr( process, s.AddrPC.Offset, &offset_from_symbol, &line )) 
#endif
          os << "\t" << line.FileName << "(" << line.LineNumber << ")";
    }
    else
      os << "(No Symbols: PC == 0)";
    ++frame_number;
  } while(s.AddrReturn.Offset != 0);
  return true;
}

class get_mod_info {
  HANDLE process;
  static const int buffer_length = 4096;
public:
  get_mod_info(HANDLE h) : process(h) {}

  module_data operator()(HMODULE module) { 
    module_data ret;
    char temp[buffer_length];
    MODULEINFO mi;

    GetModuleInformation(process, module, &mi, sizeof(mi));
    ret.base_address = mi.lpBaseOfDll;
    ret.load_size = mi.SizeOfImage;

    GetModuleFileNameExA(process, module, temp, sizeof(temp));
    ret.image_name = temp;
    GetModuleBaseNameA(process, module, temp, sizeof(temp));
    ret.module_name = temp;
    std::vector<char> img(ret.image_name.begin(), ret.image_name.end());
    std::vector<char> mod(ret.module_name.begin(), ret.module_name.end());
#ifdef _M_X64
    SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, ret.load_size);
#else
        SymLoadModule(process, 0, &img[0], &mod[0], (uint32_t)ret.base_address, ret.load_size);
#endif
    return ret;
  }
};

void *load_modules_symbols(HANDLE process, uint32_t /*pid*/) {
  ModuleList modules;

  unsigned long cbNeeded;
  std::vector<HMODULE> module_handles(1);

  EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded);
  module_handles.resize(cbNeeded/sizeof(HMODULE));
  EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded);

  std::transform(module_handles.begin(), module_handles.end(), std::back_inserter(modules), get_mod_info(process));
  return modules[0].base_address;
}

#endif // (!defined __linux__ && !defined __MINGW32__)

использование в main.cpp

#if(!defined __linux__ && !defined __MINGW32__)
extern LONG WINAPI myUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *ep);
#endif

int main(int argc, char *argv[])
{
#if(!defined __linux__ && !defined __MINGW32__)
    SetUnhandledExceptionFilter(myUnhandledExceptionFilter);
#endif
luntik2012
()
Ответ на: комментарий от luntik2012

Спасибо за код. В принципе, такую же инфу уже дает Valgrind. Всё же, AddressSanitizer и прочие плагины дают более продвинутый результат, попробую воспользоваться этими инструментами

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от I-Love-Microsoft

Пиши сразу на Расте, а лучше вообще на электроне!!1111

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

Знал что есть подобное, просто слов подобрать не мог

Сколько, говоришь, лет ты пишешь на C++? :D

anonymous
()

Есть ещё так называемое инструментирование:

LC_ALL=C man gcc | grep 'Program Instrumentation Options' -A 20

В шланге - ещё поболее всякого будет. В частности используют для наколенных инструментальных профайлеров (lttng всякие например).

Можно например инструментировать каждый вызов каждой функции и выполнять действия до и после.

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

valgrind разве можно запустить у заказчика, к тому же на винде? я почему-то подумал, что именно такая ситуация.

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

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

Сколько, говоришь, лет ты пишешь на C++?

Я пишу на Qt, а последнее время на Сях

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

В шланге - ещё поболее всякого будет

Могу кстати и его заюзать для отладочных билдов

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от luntik2012

valgrind разве можно запустить у заказчика, к тому же на винде? я почему-то подумал, что именно такая ситуация.

На винде можешь drmemory запускать, впрочем он и в линуксах работает

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

Да, спасибо, в общем это работает:

QMAKE_CFLAGS += -fsanitize=address -fstack-protector-all -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak
QMAKE_CXXFLAGS += -fsanitize=address -fstack-protector-all -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak
QMAKE_LFLAGS += -fsanitize=address -fstack-protector-all -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak
Запускаю так
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libasan.so.5.0.0 ./my_prog_name

Правда пришлось натянуть последний gcc:

sudo apt-get install gcc-8 g++-8
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8
Иначе не виделся asan.

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от SoulThreads

Спасибо всем за наводку, я визжу от радости, оно даже такое ловит:

int* test_func()
{
	int v[5];
	for(int i = 0; i < 5; i++)
	{
		v[i] = i + 1;
	}
	return &v[0];
}

void test_fails()
{
	char test[10];
//	test[11] = 0;
	int *v = test_func();
	*v = 10;
}
Всякие косяки со стеком, выходы за границы массивов и тому подобное. Жаль лет 10 назад о таком не слышал, сколько бы проблем это решило, сколько нервов спасло. А может и не было тогда такого.

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от I-Love-Microsoft

Лет пять назад вроде начали пилить в gcc после того как в LLVM появилось

Dark_SavanT ★★★★★
()

Учитывая что ты не знал про санитайзеры, возможно и clang-tidy будет для тебя чем то в новинку?

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

Конечно в новинку, но я бы не очень доверял такой штуке

Лет пять назад вроде начали пилить в gcc после того как в LLVM появилось

Dark_SavanT, не долго я в заморозке был :)

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от I-Love-Microsoft

но я бы не очень доверял такой штуке

Но почему? Она работает по той же базе данных компиляции что и всякие парсеры кода и более навороченные поделки…

На моей практике, ошибается довольно редко, плюс можно легко интегрировать в ide/редактор/ci. Находит иногда довольно забавные баги, например в gmock ишью с помощью этой поделки в 1.8.1 закрывали вроде бы.

Есть не просит, ложные срабатывания не сложно отсеять, но при этом может следить как за стилем кода, так и за дебильными ошибками, при этом интерактивно и не требует запуска целевого бинарника.

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

для таких случаев есть google breakpad. недавно кто-то задавал такой же вопрос в девелопменте.

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