Важност прототипова

31.08.2022

Радећи на писању mkfile‑а за Neomutt, сусрео сам се са необичним проблемом. При покретању, Neomutt је крахирао уз SIGSEGV, а испитивањем у GDB-у установио сам да до грешке долази у фајлу gui/terminal.c, у линијама 62-63:

char *tcaps = tigetstr("tsl");
if (tcaps && (tcaps != (char *) -1) && *tcaps)

Наиме, вредност показивача tcaps је у моменту segmentation fault‑а 0xfffffffff75de85c, што није адреса којој програми смеју приступати. Даље испитивање ме је довело до функције tigetstr(3) из NetBSD-curses‑а:

const char *
ti_getstr(const TERMINAL *term, const char *id)
{
	ssize_t ind;
	size_t i;
	TERMUSERDEF *ud;

	_DIAGASSERT(term != NULL);
	_DIAGASSERT(id != NULL);

	ind = _ti_strindex(id);
	if (ind != -1)
		return term->strs[ind]; // <-- ово се враћа...

	/* ... */
}

char *
tigetstr(const char *id)
{

	_DIAGASSERT(id != NULL);
	if (cur_term != NULL)
	{
		char* ret = ti_getstr(cur_term, id);
		return __UNCONST(ret); // <-- ... и прослеђује
				       //    макроу __UNCONST
	}
	return (char *)CANCELLED_STRING;
}

Адреса показивача term->strs[ind] који се враћа из интерне функције ti_getstr у функцију tigetstr je 0x7ffff75de85c, што је исправна адреса показивача, и у GDB‑у се приказује као знаковна ниска. Она се после cast‑овања макроом __UNCONST:

#define __UNCONST(a)   (void *)(intptr_t)(a)

враћа и додељује већ поменутом показивачу tcaps у функцији mutt_ts_capability у Neomutt‑у.

Међутим, ту долази до проблема: 0x7ffff75de85c одједном постаје 0xfffffffff75de85c. Зашто?

Испрва сам помислио да је кривац различита врста кода у односу на позицију (PIC - Position Independent Code), али поновно компајлирање NetBSD-curses‑а и Neomutt‑а уз -fPIC није ништа променило.

Затим сам покушао да направим мали тест програм, који би иницијализовао NetBSD-curses и позвао проблематичну функцију tigetstr. На моје изненађење, док је Neomutt проузроковао segmentation fault, тест програм је радио без проблема! Додавање још кода из Neomutt‑а тест програму није променило ситуацију.

Оно што ми је коначно дало идеју шта је узрок проблема је следећи одговор на питање на StackOverflow‑у:

  • channelGet() is assumed to return int (due to lack of prototype) [истакао СР], so the result is truncated to 0xf7fed1a0
  • then it is cast to a 64-bit pointer, so gets sign-extended to 0xfffffffff7fed1a0

„Претпоставља се да функција враћа int, због недостатка прототипа, па је резултат скраћен. Онда се cast‑ује у 64‑битни показивач, коме се продужава знак.“ Па наравно, недостатак прототипа! Функција tigetstr захтева укључивање заглавља term.h, које није било укључено са том путањом, већ као ncurses/term.h. Што се тиче самог прототипа, у овом случају он је сасвим једноставан, без struct‑ова, али је његов недостатак ипак проузроковао SIGSEGV!


Епилог: Neomutt ипак захтева функције које недостају NetBSD-curses‑у, па ћу за његово коришћење по свој прилици морати или да их сам напишем, или да пређем на гломазни ncurses, али сам из целе ствари имао прилике да научим нешто ново.


Generated by slweb © 2020-2024 Strahinya Radich.