Commit b2360f15 authored by Paulo Zanoni's avatar Paulo Zanoni
Browse files

iris: check memory with INTEL_DEBUG=mem



After this commit, INTEL_DEBUG=mem will make iris over-allocate a page
for every GEM buffer requested, write a pattern to the unused end area
- which we refer to as the red zone - and check for integrity of the
pattern when the buffer is not used anymore. Once a problem is found,
we dump the buffer and assert(false). Notice that the red zone is not
only the extra page we over-allocate, but it also includes the padding
caused by iris' allocation buckets and page rounding: even if our tool
didn't over-allocate anything (i.e., if we define
RED_ZONE_BUFFER_EXTENSION to zero), the tool would still be doing
helpful work.

While I was able to find an issue in the Kernel with this patch
series, it turns our the Mesa problems I suspected to have found were
issues with this patch. So as of v5 the memory checker still has yet
to find a Mesa memory problem.

I also plan to extend the subsystem to also check for use-after-free
issues in later patches, but first I want to work on the bugs that red
zoning was already able to catch.

v2:
 - Rebase
v3:
 - Missing commas in error message (Marcin Ślusarz).
 - Rely on memcmp() in check_red_zone() instead of implementing our
   own uint64_t check (Marcin Ślusarz).
v4:
 - Rebase.
 - Tweak the dump_mem_range() output after having used it for a while.
 - Make the code a little easier to read.
v5:
 - Use RAW_MODE so we don't get false positives in RBC and HIZ
   auxiliary buffers.
Signed-off-by: Paulo Zanoni's avatarPaulo Zanoni <paulo.r.zanoni@intel.com>
parent 6723addd
Pipeline #189549 waiting for manual action with stages
......@@ -380,6 +380,108 @@ bo_calloc(void)
return bo;
}
static void dump_mem_range(unsigned char *mem, size_t start, size_t end)
{
const int per_line = 32;
unsigned char line[per_line], prev_line[per_line];
size_t l, i, used;
int repetitions = 0;
const int rep_threshold = 32;
for (l = start; l < end; l += per_line) {
for (i = 0; i < per_line && l + i < end; i++)
line[i] = mem[l + i];
used = i;
const bool is_last_line = l + per_line >= end;
if (l != start && !is_last_line &&
memcmp(prev_line, line, per_line) == 0)
repetitions++;
else
repetitions = 0;
if (repetitions == rep_threshold) {
fprintf(stderr, "0x%08zX+ (%010zu+): (... pattern repeats ...)\n", l, l);
} else if (repetitions < rep_threshold) {
fprintf(stderr, "0x%08zX (%010zu): ", l, l);
for (i = 0; i < used; i++)
fprintf(stderr, "%02X", line[i]);
fprintf(stderr, "\n");
}
memcpy(prev_line, line, per_line);
}
}
static void dump_bad_bo(struct iris_bo *bo)
{
unsigned char *map = iris_bo_map(NULL, bo, MAP_READ | MAP_RAW);
assert(map);
fprintf(stderr, "found red zone issue: bo handle %"PRIu32", name \"%s\", "
"orig_size %"PRIu64", size:%"PRIu64"\n",
bo->gem_handle, bo->name ? bo->name : "(no name)", bo->orig_size,
bo->size);
/* May be handy while debugging, but eats all your scrollback. */
if (0) {
fprintf(stderr, "Real buffer:\n");
dump_mem_range(map, 0, bo->orig_size);
fprintf(stderr, "\n");
}
fprintf(stderr, "Red zone:\n");
dump_mem_range(map, bo->orig_size, bo->size);
}
/* How much we extend the buffers for red zone checking. */
#define RED_ZONE_BUFFER_EXTENSION 4096
/* What we write after the buffer: detects overflow. */
#define RED_ZONE_VALUE 0xCA
#define RED_ZONE_VALUE_64 0xCACACACACACACACAULL
static void write_red_zone(struct iris_bo *bo)
{
void *map;
assert(INTEL_DEBUG & DEBUG_MEMORY);
assert(bo->orig_size);
map = iris_bo_map(NULL, bo, MAP_WRITE | MAP_RAW);
assert(map);
memset(map + bo->orig_size, RED_ZONE_VALUE, bo->size - bo->orig_size);
}
static void check_red_zone(struct iris_bo *bo)
{
unsigned char *map;
uint64_t *tmp;
assert(INTEL_DEBUG & DEBUG_MEMORY);
assert(bo->idle);
assert(bo->has_red_zone);
assert(bo->orig_size);
if (bo->size == bo->orig_size)
return;
map = iris_bo_map(NULL, bo, MAP_READ | MAP_RAW);
assert(map);
/* Try to be efficient when checking that everything from orig_size to size
* is RED_ZONE_VALUE. The memcmp() function really likes when both pointers
* are aligned, that's why we do this.
*/
STATIC_ASSERT(RED_ZONE_BUFFER_EXTENSION > 8);
assert(bo->orig_size + 8 < bo->size);
tmp = (uint64_t *)&map[bo->orig_size];
if (!(*tmp == RED_ZONE_VALUE_64 &&
memcmp(&map[bo->orig_size], &map[bo->orig_size + 8],
bo->size - bo->orig_size - 8) == 0))
dump_bad_bo(bo);
}
static struct iris_bo *
alloc_bo_from_cache(struct iris_bufmgr *bufmgr,
struct bo_cache_bucket *bucket,
......@@ -405,6 +507,8 @@ alloc_bo_from_cache(struct iris_bufmgr *bufmgr,
if (iris_bo_busy(cur))
return NULL;
assert(!cur->has_red_zone);
list_del(&cur->head);
/* Tell the kernel we need this BO. If it still exists, we're done! */
......@@ -512,7 +616,17 @@ bo_alloc_internal(struct iris_bufmgr *bufmgr,
uint32_t stride)
{
struct iris_bo *bo;
uint64_t orig_size = size;
unsigned int page_size = getpagesize();
bool add_red_zone = false;
if (unlikely((INTEL_DEBUG & DEBUG_MEMORY) &&
(memzone != IRIS_MEMZONE_BINDER &&
memzone != IRIS_MEMZONE_BORDER_COLOR_POOL))) {
add_red_zone = true;
size += RED_ZONE_BUFFER_EXTENSION;
}
struct bo_cache_bucket *bucket = bucket_for_size(bufmgr, size);
/* Round the size up to the bucket size, or if we don't have caching
......@@ -554,6 +668,11 @@ bo_alloc_internal(struct iris_bufmgr *bufmgr,
if (bo_set_tiling_internal(bo, tiling_mode, stride))
goto err_free;
bo->orig_size = orig_size;
bo->has_red_zone = add_red_zone;
if (unlikely(add_red_zone))
write_red_zone(bo);
bo->name = name;
p_atomic_set(&bo->refcount, 1);
bo->reusable = bucket && bufmgr->bo_reuse;
......@@ -749,6 +868,8 @@ bo_close(struct iris_bo *bo)
{
struct iris_bufmgr *bufmgr = bo->bufmgr;
assert(!bo->has_red_zone);
if (bo->external) {
struct hash_entry *entry;
......@@ -862,6 +983,12 @@ bo_unreference_final(struct iris_bo *bo, time_t time)
DBG("bo_unreference final: %d (%s)\n", bo->gem_handle, bo->name);
if (unlikely(bo->has_red_zone)) {
iris_bo_wait_rendering(bo);
check_red_zone(bo);
bo->has_red_zone = false;
}
bucket = NULL;
if (bo->reusable)
bucket = bucket_for_size(bufmgr, bo->size);
......
......@@ -127,6 +127,12 @@ struct iris_bo {
*/
uint64_t size;
/**
* Original size requested for the buffer, before alignment and bucket
* adjustments.
*/
uint64_t orig_size;
/** Buffer manager context associated with this buffer object */
struct iris_bufmgr *bufmgr;
......@@ -237,6 +243,11 @@ struct iris_bo {
* Boolean of whether this buffer points into user memory
*/
bool userptr;
/**
* Boolean of whether this buffer has a red zone for memory debug.
*/
bool has_red_zone;
};
#define BO_ALLOC_ZEROED (1<<0)
......
......@@ -94,6 +94,7 @@ static const struct debug_control debug_control[] = {
{ "pc", DEBUG_PIPE_CONTROL },
{ "nofc", DEBUG_NO_FAST_CLEAR },
{ "no32", DEBUG_NO32 },
{ "mem", DEBUG_MEMORY },
{ NULL, 0 }
};
......
......@@ -89,6 +89,7 @@ extern uint64_t INTEL_DEBUG;
#define DEBUG_PIPE_CONTROL (1ull << 45)
#define DEBUG_NO_FAST_CLEAR (1ull << 46)
#define DEBUG_NO32 (1ull << 47)
#define DEBUG_MEMORY (1ull << 48)
/* These flags are not compatible with the disk shader cache */
#define DEBUG_DISK_CACHE_DISABLE_MASK DEBUG_SHADER_TIME
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment