Tests: detect task leaks (#5528)

This commit is contained in:
Martin Griffin 2024-10-22 09:19:00 +01:00 committed by GitHub
parent d11fd21cd6
commit 1421ed1b24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 3350 additions and 13 deletions

View file

@ -45,5 +45,6 @@ void SE12PanpotControl(s8 pan);
bool8 IsSEPlaying(void); bool8 IsSEPlaying(void);
bool8 IsBGMPlaying(void); bool8 IsBGMPlaying(void);
bool8 IsSpecialSEPlaying(void); bool8 IsSpecialSEPlaying(void);
void Task_DuckBGMForPokemonCry(u8 taskId);
#endif // GUARD_SOUND_H #endif // GUARD_SOUND_H

View file

@ -1780,7 +1780,20 @@ void CB2_QuitRecordedBattle(void)
m4aMPlayStop(&gMPlayInfo_SE1); m4aMPlayStop(&gMPlayInfo_SE1);
m4aMPlayStop(&gMPlayInfo_SE2); m4aMPlayStop(&gMPlayInfo_SE2);
if (gTestRunnerEnabled) if (gTestRunnerEnabled)
{
// Clean up potentially-leaking tasks.
// I think these leak when the battle ends soon after a
// battler is fainted.
u8 taskId;
taskId = FindTaskIdByFunc(Task_PlayerController_RestoreBgmAfterCry);
if (taskId != TASK_NONE)
DestroyTask(taskId);
taskId = FindTaskIdByFunc(Task_DuckBGMForPokemonCry);
if (taskId != TASK_NONE)
DestroyTask(taskId);
TestRunner_Battle_AfterLastTurn(); TestRunner_Battle_AfterLastTurn();
}
FreeRestoreBattleData(); FreeRestoreBattleData();
FreeAllWindowBuffers(); FreeAllWindowBuffers();
SetMainCallback2(gMain.savedCallback); SetMainCallback2(gMain.savedCallback);

View file

@ -31,7 +31,6 @@ extern struct ToneData gCryTable_Reverse[];
static void Task_Fanfare(u8 taskId); static void Task_Fanfare(u8 taskId);
static void CreateFanfareTask(void); static void CreateFanfareTask(void);
static void Task_DuckBGMForPokemonCry(u8 taskId);
static void RestoreBGMVolumeAfterPokemonCry(void); static void RestoreBGMVolumeAfterPokemonCry(void);
static const struct Fanfare sFanfares[] = { static const struct Fanfare sFanfares[] = {
@ -513,7 +512,7 @@ bool8 IsCryPlaying(void)
return FALSE; return FALSE;
} }
static void Task_DuckBGMForPokemonCry(u8 taskId) void Task_DuckBGMForPokemonCry(u8 taskId)
{ {
if (gPokemonCryBGMDuckingCounter) if (gPokemonCryBGMDuckingCounter)
{ {

View file

@ -5,6 +5,7 @@
#include "main.h" #include "main.h"
#include "malloc.h" #include "malloc.h"
#include "random.h" #include "random.h"
#include "task.h"
#include "constants/characters.h" #include "constants/characters.h"
#include "test_runner.h" #include "test_runner.h"
#include "test/test.h" #include "test/test.h"
@ -190,6 +191,7 @@ top:
else else
gTestRunnerState.timeoutSeconds = UINT_MAX; gTestRunnerState.timeoutSeconds = UINT_MAX;
InitHeap(gHeap, HEAP_SIZE); InitHeap(gHeap, HEAP_SIZE);
ResetTasks();
EnableInterrupts(INTR_FLAG_TIMER2); EnableInterrupts(INTR_FLAG_TIMER2);
REG_TM2CNT_L = UINT16_MAX - (274 * 60); // Approx. 1 second. REG_TM2CNT_L = UINT16_MAX - (274 * 60); // Approx. 1 second.
REG_TM2CNT_H = TIMER_ENABLE | TIMER_INTR_ENABLE | TIMER_1024CLK; REG_TM2CNT_H = TIMER_ENABLE | TIMER_INTR_ENABLE | TIMER_1024CLK;
@ -243,6 +245,7 @@ top:
if (gTestRunnerState.result == TEST_RESULT_PASS if (gTestRunnerState.result == TEST_RESULT_PASS
&& !gTestRunnerState.expectLeaks) && !gTestRunnerState.expectLeaks)
{ {
int i;
const struct MemBlock *head = HeapHead(); const struct MemBlock *head = HeapHead();
const struct MemBlock *block = head; const struct MemBlock *block = head;
do do
@ -251,7 +254,7 @@ top:
|| !(EWRAM_START <= (uintptr_t)block->next && (uintptr_t)block->next < EWRAM_END) || !(EWRAM_START <= (uintptr_t)block->next && (uintptr_t)block->next < EWRAM_END)
|| (block->next <= block && block->next != head)) || (block->next <= block && block->next != head))
{ {
Test_MgbaPrintf("gHeap corrupted block at 0x%p", block); Test_MgbaPrintf("gHeap corrupted block at %p", block);
gTestRunnerState.result = TEST_RESULT_ERROR; gTestRunnerState.result = TEST_RESULT_ERROR;
break; break;
} }
@ -268,6 +271,15 @@ top:
block = block->next; block = block->next;
} }
while (block != head); while (block != head);
for (i = 0; i < NUM_TASKS; i++)
{
if (gTasks[i].isActive)
{
Test_MgbaPrintf("%p: task not freed", gTasks[i].func);
gTestRunnerState.result = TEST_RESULT_FAIL;
}
}
} }
if (gTestRunnerState.test->runner == &gAssumptionsRunner) if (gTestRunnerState.test->runner == &gAssumptionsRunner)
@ -585,6 +597,9 @@ static s32 MgbaVPrintf_(const char *fmt, va_list va)
p = va_arg(va, unsigned); p = va_arg(va, unsigned);
{ {
s32 n; s32 n;
i = MgbaPutchar_(i, '<');
i = MgbaPutchar_(i, '0');
i = MgbaPutchar_(i, 'x');
for (n = 0; n < 7; n++) for (n = 0; n < 7; n++)
{ {
unsigned nybble = (p >> (24 - (4*n))) & 0xF; unsigned nybble = (p >> (24 - (4*n))) & 0xF;
@ -593,6 +608,7 @@ static s32 MgbaVPrintf_(const char *fmt, va_list va)
else else
i = MgbaPutchar_(i, 'a' + nybble - 10); i = MgbaPutchar_(i, 'a' + nybble - 10);
} }
i = MgbaPutchar_(i, '>');
} }
break; break;
case 'q': case 'q':

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,7 @@
#include <regex.h> #include <regex.h>
#include <signal.h> #include <signal.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -32,6 +33,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "elf.h"
#define min(a, b) ((a) < (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b))
@ -69,10 +71,103 @@ struct Runner
char assumeFailed_FilenameLine[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; char assumeFailed_FilenameLine[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH];
}; };
struct Symbol {
const char *name;
uint32_t address;
size_t size;
};
struct SymbolTable {
struct Symbol *symbols;
size_t symbols_n;
};
static unsigned nrunners = 0; static unsigned nrunners = 0;
static unsigned runners_digits = 0; static unsigned runners_digits = 0;
static struct Runner *runners = NULL; static struct Runner *runners = NULL;
// TODO: Build the symbol table on demand.
static struct SymbolTable symbol_table = { NULL, 0 };
static const struct Symbol *lookup_address(uint32_t address)
{
int lo = 0, hi = symbol_table.symbols_n;
while (lo < hi)
{
int mi = lo + (hi - lo) / 2;
const struct Symbol *symbol = &symbol_table.symbols[mi];
if (address < symbol->address)
hi = mi;
else if (address >= symbol->address + symbol->size)
lo = mi + 1;
else
return symbol;
}
return NULL;
}
// Similar to 'fwrite(buffer, 1, size, f)' except that anything which
// looks like the output of '%p' (i.e. '<0x\d{7}>') is translated into
// the name of a symbol (if it represents one).
static void fprint_buffer(FILE *f, const char *buffer, size_t size)
{
const char *buffer_end = buffer + size;
while (buffer < buffer_end)
{
// Find the next '<0x'.
char *buffer_ = memmem(buffer, buffer_end - buffer, "<0x", 3);
// No '<0x' or could not possibly match, print everything.
if (buffer_ == NULL || buffer_end - buffer_ < 11 || buffer_[10] != '>')
{
fwrite(buffer, 1, buffer_end - buffer, f);
break;
}
// Print everything before the '<0x'.
fwrite(buffer, 1, buffer_ - buffer, f);
buffer = buffer_;
unsigned long address = strtoul(buffer + 3, &buffer_, 16);
// Un-mirror EWRAM/IWRAM/ROM addresses.
switch (address & 0xF000000)
{
case 0x2000000: address = address & 0x203FFFF; break;
case 0x3000000: address = address & 0x3007FFF; break;
case 0x7000000: address = address & 0x70003FF; break;
case 0xA000000: address = address & 0x9FFFFFF; break;
case 0xB000000: address = address & 0x9FFFFFF; break;
case 0xC000000: address = address & 0x9FFFFFF; break;
case 0xD000000: address = address & 0x9FFFFFF; break;
}
// Not a 7-digit address, print the '<0x' part and loop.
if (buffer_ != buffer + 10)
{
fwrite(buffer, 1, 3, f);
buffer += 3;
continue;
}
const struct Symbol *symbol = lookup_address(address);
// Not a symbol, print the parsed part and loop.
if (symbol == NULL)
{
fwrite(buffer, 1, 11, f);
buffer += 11;
continue;
}
if (symbol->address == address)
fprintf(f, "<%s>", symbol->name);
else
fprintf(f, "<%s+0x%lx>", symbol->name, address - symbol->address);
buffer += 11;
}
}
static void handle_read(int i, struct Runner *runner) static void handle_read(int i, struct Runner *runner)
{ {
char *sol = runner->input_buffer; char *sol = runner->input_buffer;
@ -156,7 +251,7 @@ add_to_results:
soc += 2; soc += 2;
fprintf(stdout, "[%0*d] %s: ", runners_digits, i, runner->test_name); fprintf(stdout, "[%0*d] %s: ", runners_digits, i, runner->test_name);
fwrite(soc, 1, eol - soc, stdout); fwrite(soc, 1, eol - soc, stdout);
fwrite(runner->output_buffer, 1, runner->output_buffer_size, stdout); fprint_buffer(stdout, runner->output_buffer, runner->output_buffer_size);
strcpy(runner->test_name, "WAITING..."); strcpy(runner->test_name, "WAITING...");
runner->output_buffer_size = 0; runner->output_buffer_size = 0;
break; break;
@ -186,11 +281,7 @@ buffer_output:
} }
else else
{ {
if (write(STDOUT_FILENO, sol, eol - sol) == -1) fwrite(sol, 1, eol - sol, stdout);
{
perror("write failed");
exit(2);
}
} }
sol += n; sol += n;
consumed += n; consumed += n;
@ -233,12 +324,80 @@ static void exit2(int _)
exit(2); exit(2);
} }
int compare_strings(const void * a, const void * b) static int compare_addresses(const void *a, const void *b)
{ {
const char *arg1 = (const char *) a; const struct Symbol *sa = a, *sb = b;
const char *arg2 = (const char *) b; if (sa->address < sb->address)
return -1;
else if (sa->address == sb->address)
return 0;
else
return 1;
}
return strcmp(arg1, arg2); static void build_symbol_table(void *elf)
{
if (memcmp(elf, ELFMAG, 4) != 0)
goto error;
size_t symbol_table_symbols_c = 1024;
symbol_table.symbols = malloc(symbol_table_symbols_c * sizeof(*symbol_table.symbols));
if (symbol_table.symbols == NULL)
goto error;
const Elf32_Ehdr *ehdr = (Elf32_Ehdr *)elf;
const Elf32_Shdr *shdrs = (Elf32_Shdr *)(elf + ehdr->e_shoff);
if (ehdr->e_shstrndx == SHN_UNDEF)
goto error;
const Elf32_Shdr *shdr_shstr = &shdrs[ehdr->e_shstrndx];
const char *shstr = (const char *)(elf + shdr_shstr->sh_offset);
const Elf32_Shdr *shdr_symtab = NULL;
const Elf32_Shdr *shdr_strtab = NULL;
for (int i = 0; i < ehdr->e_shnum; i++)
{
const char *sh_name = shstr + shdrs[i].sh_name;
if (strcmp(sh_name, ".symtab") == 0)
shdr_symtab = &shdrs[i];
else if (strcmp(sh_name, ".strtab") == 0)
shdr_strtab = &shdrs[i];
}
if (!shdr_symtab)
goto error;
if (!shdr_strtab)
goto error;
const Elf32_Sym *symtab = (Elf32_Sym *)(elf + shdr_symtab->sh_offset);
const char *strtab = (const char *)(elf + shdr_strtab->sh_offset);
for (int i = 0; i < shdr_symtab->sh_size / shdr_symtab->sh_entsize; i++)
{
if (symtab[i].st_name == 0) continue;
if (symtab[i].st_shndx > ehdr->e_shnum) continue;
if (symtab[i].st_value < 0x2000000 || symtab[i].st_size == 0) continue;
struct Symbol symbol =
{
.name = strtab + symtab[i].st_name,
.address = symtab[i].st_value,
.size = symtab[i].st_size,
};
if (symbol_table.symbols_n == symbol_table_symbols_c)
{
symbol_table_symbols_c *= 2;
void *symbols = realloc(symbol_table.symbols, symbol_table_symbols_c * sizeof(*symbol_table.symbols));
if (symbols == NULL)
goto error;
symbol_table.symbols = symbols;
}
symbol_table.symbols[symbol_table.symbols_n++] = symbol;
}
qsort(symbol_table.symbols, symbol_table.symbols_n, sizeof(*symbol_table.symbols), compare_addresses);
return;
error:
free(symbol_table.symbols);
symbol_table.symbols = NULL;
symbol_table.symbols_n = 0;
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])
@ -292,6 +451,8 @@ int main(int argc, char *argv[])
exit(2); exit(2);
} }
build_symbol_table(elf);
nrunners = 1; nrunners = 1;
const char *makeflags = getenv("MAKEFLAGS"); const char *makeflags = getenv("MAKEFLAGS");
if (makeflags) if (makeflags)