Angus Gratton 2024-04-25 22:52:06 +02:00 zatwierdzone przez GitHub
commit fcd2cda38a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
4 zmienionych plików z 147 dodań i 14 usunięć

78
py/gc.c
Wyświetl plik

@ -26,16 +26,14 @@
*/
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "py/gc.h"
#include "py/runtime.h"
#if MICROPY_DEBUG_VALGRIND
#include <valgrind/memcheck.h>
#endif
#if MICROPY_ENABLE_GC
#if MICROPY_DEBUG_VERBOSE // print debugging info
@ -120,6 +118,8 @@
#define GC_EXIT()
#endif
#include "py/gc_valgrind.h"
// TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool
static void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) {
// calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes):
@ -170,6 +170,12 @@ static void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) {
area->next = NULL;
#endif
// Valgrind: Assume 'area' came to us from malloc, so resize it such that it
// doesn't cover the 'pool' area
//
// This frees up the area between gc_pool_start and gc_pool_end to have its allocations tracked.
VALGRIND_RESIZEINPLACE_BLOCK(start, end - start, area->gc_pool_start - (uint8_t *)start, 0);
DEBUG_printf("GC layout:\n");
DEBUG_printf(" alloc table at %p, length " UINT_FMT " bytes, "
UINT_FMT " blocks\n",
@ -404,9 +410,15 @@ static void gc_mark_subtree(size_t block)
// check that the consecutive blocks didn't overflow past the end of the area
assert(area->gc_pool_start + (block + n_blocks) * BYTES_PER_BLOCK <= area->gc_pool_end);
size_t n_bytes = n_blocks * BYTES_PER_BLOCK;
#if MICROPY_DEBUG_VALGRIND
// Only search the real allocation size so valgrind doesn't complain
n_bytes = valgrind_get_alloc_sz((void *)PTR_FROM_BLOCK(area, block), n_blocks);
#endif
// check this block's children
void **ptrs = (void **)PTR_FROM_BLOCK(area, block);
for (size_t i = n_blocks * BYTES_PER_BLOCK / sizeof(void *); i > 0; i--, ptrs++) {
for (size_t i = n_bytes / sizeof(void *); i > 0; i--, ptrs++) {
MICROPY_GC_HOOK_LOOP(i);
void *ptr = *ptrs;
// If this is a heap pointer that hasn't been marked, mark it and push
@ -525,6 +537,7 @@ static void gc_sweep(void) {
#if MICROPY_PY_GC_COLLECT_RETVAL
MP_STATE_MEM(gc_collected)++;
#endif
VALGRIND_MP_FREE(PTR_FROM_BLOCK(area, block));
// fall through to free the head
MP_FALLTHROUGH
@ -592,15 +605,15 @@ void gc_collect_start(void) {
__attribute__((no_sanitize_address))
#endif
static void *gc_get_ptr(void **ptrs, int i) {
#if MICROPY_DEBUG_VALGRIND
if (!VALGRIND_CHECK_MEM_IS_ADDRESSABLE(&ptrs[i], sizeof(*ptrs))) {
return NULL;
}
#endif
return ptrs[i];
}
void gc_collect_root(void **ptrs, size_t len) {
#if MICROPY_DEBUG_VALGRIND
// ptrs may include undefined words on the stack, tell valgrind this is OK
VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(ptrs, len * sizeof(*ptrs));
#endif
#if !MICROPY_GC_SPLIT_HEAP
mp_state_mem_area_t *area = &MP_STATE_MEM(area);
#endif
@ -636,9 +649,11 @@ void gc_collect_end(void) {
#if MICROPY_GC_SPLIT_HEAP
MP_STATE_MEM(gc_last_free_area) = &MP_STATE_MEM(area);
#endif
#if !MICROPY_DEBUG_VALGRIND_MAX_ADDRSPACE
for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
area->gc_last_free_atb_index = 0;
}
#endif
MP_STATE_THREAD(gc_lock_depth)--;
GC_EXIT();
}
@ -825,6 +840,9 @@ found:
#endif
area->gc_last_free_atb_index = (i + 1) / BLOCKS_PER_ATB;
}
#if MICROPY_DEBUG_VALGRIND_MAX_ADDRSPACE
area->gc_last_free_atb_index = (i + 1) / BLOCKS_PER_ATB;
#endif
area->gc_last_used_block = MAX(area->gc_last_used_block, end_block);
@ -848,18 +866,29 @@ found:
GC_EXIT();
// The number of bytes allocated from the heap
size_t block_byte_len = (end_block - start_block + 1) * BYTES_PER_BLOCK;
// Valgrind: Mark the whole block as accessible so that gc can zero bytes if needed,
// without registering this as an allocation
VALGRIND_MAKE_MEM_UNDEFINED(ret_ptr, block_byte_len);
#if MICROPY_GC_CONSERVATIVE_CLEAR
// be conservative and zero out all the newly allocated blocks
memset((byte *)ret_ptr, 0, (end_block - start_block + 1) * BYTES_PER_BLOCK);
memset((byte *)ret_ptr, 0, block_byte_len);
#else
// zero out the additional bytes of the newly allocated blocks
// This is needed because the blocks may have previously held pointers
// to the heap and will not be set to something else if the caller
// doesn't actually use the entire block. As such they will continue
// to point to the heap and may prevent other blocks from being reclaimed.
memset((byte *)ret_ptr + n_bytes, 0, (end_block - start_block + 1) * BYTES_PER_BLOCK - n_bytes);
memset((byte *)ret_ptr + n_bytes, 0, block_byte_len - n_bytes);
#endif
// Valgrind: Mark the region as no-access again, then track the real allocation
VALGRIND_MAKE_MEM_NOACCESS(ret_ptr, block_byte_len);
VALGRIND_MP_MALLOC(ret_ptr, n_bytes);
#if MICROPY_ENABLE_FINALISER
if (has_finaliser) {
// clear type pointer in case it is never set
@ -942,10 +971,12 @@ void gc_free(void *ptr) {
}
#endif
#if !MICROPY_DEBUG_VALGRIND_MAX_ADDRSPACE
// set the last_free pointer to this block if it's earlier in the heap
if (block / BLOCKS_PER_ATB < area->gc_last_free_atb_index) {
area->gc_last_free_atb_index = block / BLOCKS_PER_ATB;
}
#endif
// free head and all of its tail blocks
do {
@ -953,6 +984,8 @@ void gc_free(void *ptr) {
block += 1;
} while (ATB_GET_KIND(area, block) == AT_TAIL);
VALGRIND_MP_FREE(ptr);
GC_EXIT();
#if EXTENSIVE_HEAP_PROFILING
@ -1084,7 +1117,12 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
// return original ptr if it already has the requested number of blocks
if (new_blocks == n_blocks) {
VALGRIND_MP_RESIZE_BLOCK(ptr, n_blocks, n_bytes);
GC_EXIT();
DEBUG_printf("gc_realloc(%p -> %p, %d bytes -> %d blocks)\n", ptr_in, ptr_in, n_bytes, new_blocks);
return ptr_in;
}
@ -1102,10 +1140,14 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
}
#endif
#if !MICROPY_DEBUG_VALGRIND_MAX_ADDRSPACE
// set the last_free pointer to end of this block if it's earlier in the heap
if ((block + new_blocks) / BLOCKS_PER_ATB < area->gc_last_free_atb_index) {
area->gc_last_free_atb_index = (block + new_blocks) / BLOCKS_PER_ATB;
}
#endif
VALGRIND_MP_RESIZE_BLOCK(ptr, n_blocks, n_bytes);
GC_EXIT();
@ -1127,6 +1169,9 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
area->gc_last_used_block = MAX(area->gc_last_used_block, end_block);
// Valgrind: grow allocation to full block size, as we're about to zero all blocks
VALGRIND_MP_RESIZE_BLOCK(ptr, n_blocks, new_blocks * BYTES_PER_BLOCK);
GC_EXIT();
#if MICROPY_GC_CONSERVATIVE_CLEAR
@ -1137,6 +1182,9 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
memset((byte *)ptr_in + n_bytes, 0, new_blocks * BYTES_PER_BLOCK - n_bytes);
#endif
// Valgrind: Shrink the allocation back to the real size
VALGRIND_MP_RESIZE_BLOCK(ptr, new_blocks, n_bytes);
#if EXTENSIVE_HEAP_PROFILING
gc_dump_alloc_table(&mp_plat_print);
#endif
@ -1165,7 +1213,11 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
return NULL;
}
DEBUG_printf("gc_realloc(%p -> %p)\n", ptr_in, ptr_out);
DEBUG_printf("gc_realloc(%p -> %p, %d bytes -> %d blocks)\n", ptr_in, ptr_out, n_bytes, new_blocks);
// Valgrind: memcpy copies all blocks, so grow the previous allocation to match
VALGRIND_MP_RESIZE_BLOCK(ptr_in, n_blocks, n_blocks * BYTES_PER_BLOCK);
memcpy(ptr_out, ptr_in, n_blocks * BYTES_PER_BLOCK);
gc_free(ptr_in);
return ptr_out;

75
py/gc_valgrind.h 100644
Wyświetl plik

@ -0,0 +1,75 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2024 Angus Gratton
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_PY_GC_VALGRIND_H
#define MICROPY_INCLUDED_PY_GC_VALGRIND_H
// This header is intended for including into gc.c directly.
//
// Defining some helper macros here helps keep valgrind integration in gc.c
// as unobtrusive as possible.
#include "py/mpconfig.h"
#if MICROPY_DEBUG_VALGRIND
#include <valgrind/memcheck.h>
// MicroPython heap only knows size of an allocation in blocks,
// this function queries valgrind (if enabled) to tell us the size
// in bytes.
static size_t valgrind_get_alloc_sz(void *p, size_t num_blocks) {
size_t max_bytes = num_blocks * BYTES_PER_BLOCK;
VALGRIND_DISABLE_ERROR_REPORTING; // Checking reports an error otherwise
uintptr_t first_invalid = VALGRIND_CHECK_MEM_IS_ADDRESSABLE(p, max_bytes);
VALGRIND_ENABLE_ERROR_REPORTING;
return first_invalid ? (first_invalid - (uintptr_t)p) : max_bytes;
}
// Note: Currently we tell valgrind that the memory is zeroed if MICROPY_GC_CONSERVATIVE_CLEAR
// is set. Running with this unset results in a lot of valgrind errors!
#define VALGRIND_MP_MALLOC(PTR, LEN_BYTES) \
VALGRIND_MALLOCLIKE_BLOCK((PTR), (LEN_BYTES), 0, MICROPY_GC_CONSERVATIVE_CLEAR);
// Tell valgrind the block at PTR was OLD_NUM_BLOCKS in length, now NEW_LEN_BYTES in length
#define VALGRIND_MP_RESIZE_BLOCK(PTR, OLD_NUM_BLOCKS, NEW_LEN_BYTES) \
VALGRIND_RESIZEINPLACE_BLOCK((PTR), valgrind_get_alloc_sz((PTR), (OLD_NUM_BLOCKS)), NEW_LEN_BYTES, 0)
#define VALGRIND_MP_FREE(PTR) VALGRIND_FREELIKE_BLOCK((PTR), 0)
#else // MICROPY_DEBUG_VALGRIND
// No-op definitions
#define VALGRIND_MP_MALLOC(...)
#define VALGRIND_MP_RESIZE_BLOCK(...)
#define VALGRIND_MP_FREE(...)
#define VALGRIND_RESIZEINPLACE_BLOCK(...)
#define VALGRIND_MAKE_MEM_UNDEFINED(...)
#define VALGRIND_MAKE_MEM_NOACCESS(...)
#endif // MICROPY_DEBUG_VALGRIND
#endif // MICROPY_INCLUDED_PY_GC_VALGRIND_H

Wyświetl plik

@ -52,7 +52,7 @@ GDB = $(CROSS_COMPILE)gdb
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
SIZE = $(CROSS_COMPILE)size
STRIP = $(CROSS_COMPILE)strip
STRIP ?= $(CROSS_COMPILE)strip
AR = $(CROSS_COMPILE)ar
WINDRES = $(CROSS_COMPILE)windres

Wyświetl plik

@ -540,6 +540,12 @@
#define MICROPY_DEBUG_VALGRIND (0)
#endif
// Whether valgrind should always use new memory addresses for allocations,
// making it easier to find use-after-free bugs.
#ifndef MICROPY_DEBUG_VALGRIND_MAX_ADDRSPACE
#define MICROPY_DEBUG_VALGRIND_MAX_ADDRSPACE (MICROPY_DEBUG_VALGRIND)
#endif
/*****************************************************************************/
/* Optimisations */