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 IsBGMPlaying(void);
bool8 IsSpecialSEPlaying(void);
void Task_DuckBGMForPokemonCry(u8 taskId);
#endif // GUARD_SOUND_H

View file

@ -1780,7 +1780,20 @@ void CB2_QuitRecordedBattle(void)
m4aMPlayStop(&gMPlayInfo_SE1);
m4aMPlayStop(&gMPlayInfo_SE2);
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();
}
FreeRestoreBattleData();
FreeAllWindowBuffers();
SetMainCallback2(gMain.savedCallback);

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,7 @@
#include <regex.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -32,6 +33,7 @@
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include "elf.h"
#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];
};
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 runners_digits = 0;
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)
{
char *sol = runner->input_buffer;
@ -156,7 +251,7 @@ add_to_results:
soc += 2;
fprintf(stdout, "[%0*d] %s: ", runners_digits, i, runner->test_name);
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...");
runner->output_buffer_size = 0;
break;
@ -186,11 +281,7 @@ buffer_output:
}
else
{
if (write(STDOUT_FILENO, sol, eol - sol) == -1)
{
perror("write failed");
exit(2);
}
fwrite(sol, 1, eol - sol, stdout);
}
sol += n;
consumed += n;
@ -233,12 +324,80 @@ static void exit2(int _)
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 char *arg2 = (const char *) b;
const struct Symbol *sa = a, *sb = 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[])
@ -292,6 +451,8 @@ int main(int argc, char *argv[])
exit(2);
}
build_symbol_table(elf);
nrunners = 1;
const char *makeflags = getenv("MAKEFLAGS");
if (makeflags)