Differential D12815 Diff 40474 processor-trace/24982c1a6fce48f1e416461d42899805f74fbb26/libipt/src/pt_image_section_cache.c
Changeset View
Changeset View
Standalone View
Standalone View
processor-trace/24982c1a6fce48f1e416461d42899805f74fbb26/libipt/src/pt_image_section_cache.c
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/* | |||||
* Copyright (c) 2016-2018, Intel Corporation | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are met: | |||||
* | |||||
* * Redistributions of source code must retain the above copyright notice, | |||||
* this list of conditions and the following disclaimer. | |||||
* * Redistributions in binary form must reproduce the above copyright notice, | |||||
* this list of conditions and the following disclaimer in the documentation | |||||
* and/or other materials provided with the distribution. | |||||
* * Neither the name of Intel Corporation nor the names of its contributors | |||||
* may be used to endorse or promote products derived from this software | |||||
* without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||||
* POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#include "pt_image_section_cache.h" | |||||
#include "pt_section.h" | |||||
#include "intel-pt.h" | |||||
#include <stdlib.h> | |||||
static char *dupstr(const char *str) | |||||
{ | |||||
char *dup; | |||||
size_t len; | |||||
if (!str) | |||||
return NULL; | |||||
len = strlen(str); | |||||
dup = malloc(len + 1); | |||||
if (!dup) | |||||
return NULL; | |||||
return strcpy(dup, str); | |||||
} | |||||
int pt_iscache_init(struct pt_image_section_cache *iscache, const char *name) | |||||
{ | |||||
if (!iscache) | |||||
return -pte_internal; | |||||
memset(iscache, 0, sizeof(*iscache)); | |||||
iscache->limit = UINT64_MAX; | |||||
if (name) { | |||||
iscache->name = dupstr(name); | |||||
if (!iscache->name) | |||||
return -pte_nomem; | |||||
} | |||||
#if defined(FEATURE_THREADS) | |||||
{ | |||||
int errcode; | |||||
errcode = mtx_init(&iscache->lock, mtx_plain); | |||||
if (errcode != thrd_success) | |||||
return -pte_bad_lock; | |||||
} | |||||
#endif /* defined(FEATURE_THREADS) */ | |||||
return 0; | |||||
} | |||||
void pt_iscache_fini(struct pt_image_section_cache *iscache) | |||||
{ | |||||
if (!iscache) | |||||
return; | |||||
(void) pt_iscache_clear(iscache); | |||||
free(iscache->name); | |||||
#if defined(FEATURE_THREADS) | |||||
mtx_destroy(&iscache->lock); | |||||
#endif /* defined(FEATURE_THREADS) */ | |||||
} | |||||
static inline int pt_iscache_lock(struct pt_image_section_cache *iscache) | |||||
{ | |||||
if (!iscache) | |||||
return -pte_internal; | |||||
#if defined(FEATURE_THREADS) | |||||
{ | |||||
int errcode; | |||||
errcode = mtx_lock(&iscache->lock); | |||||
if (errcode != thrd_success) | |||||
return -pte_bad_lock; | |||||
} | |||||
#endif /* defined(FEATURE_THREADS) */ | |||||
return 0; | |||||
} | |||||
static inline int pt_iscache_unlock(struct pt_image_section_cache *iscache) | |||||
{ | |||||
if (!iscache) | |||||
return -pte_internal; | |||||
#if defined(FEATURE_THREADS) | |||||
{ | |||||
int errcode; | |||||
errcode = mtx_unlock(&iscache->lock); | |||||
if (errcode != thrd_success) | |||||
return -pte_bad_lock; | |||||
} | |||||
#endif /* defined(FEATURE_THREADS) */ | |||||
return 0; | |||||
} | |||||
static inline int isid_from_index(uint16_t index) | |||||
{ | |||||
return index + 1; | |||||
} | |||||
static int pt_iscache_expand(struct pt_image_section_cache *iscache) | |||||
{ | |||||
struct pt_iscache_entry *entries; | |||||
uint16_t capacity, target; | |||||
if (!iscache) | |||||
return -pte_internal; | |||||
capacity = iscache->capacity; | |||||
target = capacity + 8; | |||||
/* Check for overflows. */ | |||||
if (target < capacity) | |||||
return -pte_nomem; | |||||
entries = realloc(iscache->entries, target * sizeof(*entries)); | |||||
if (!entries) | |||||
return -pte_nomem; | |||||
iscache->capacity = target; | |||||
iscache->entries = entries; | |||||
return 0; | |||||
} | |||||
static int pt_iscache_find_locked(struct pt_image_section_cache *iscache, | |||||
const char *filename, uint64_t offset, | |||||
uint64_t size, uint64_t laddr) | |||||
{ | |||||
uint16_t idx, end; | |||||
if (!iscache || !filename) | |||||
return -pte_internal; | |||||
end = iscache->size; | |||||
for (idx = 0; idx < end; ++idx) { | |||||
const struct pt_iscache_entry *entry; | |||||
const struct pt_section *section; | |||||
const char *sec_filename; | |||||
uint64_t sec_offset, sec_size; | |||||
entry = &iscache->entries[idx]; | |||||
/* We do not zero-initialize the array - a NULL check is | |||||
* pointless. | |||||
*/ | |||||
section = entry->section; | |||||
sec_filename = pt_section_filename(section); | |||||
sec_offset = pt_section_offset(section); | |||||
sec_size = pt_section_size(section); | |||||
if (entry->laddr != laddr) | |||||
continue; | |||||
if (sec_offset != offset) | |||||
continue; | |||||
if (sec_size != size) | |||||
continue; | |||||
/* We should not have a section without a filename. */ | |||||
if (!sec_filename) | |||||
return -pte_internal; | |||||
if (strcmp(sec_filename, filename) != 0) | |||||
continue; | |||||
return isid_from_index(idx); | |||||
} | |||||
return 0; | |||||
} | |||||
static int pt_iscache_lru_free(struct pt_iscache_lru_entry *lru) | |||||
{ | |||||
while (lru) { | |||||
struct pt_iscache_lru_entry *trash; | |||||
int errcode; | |||||
trash = lru; | |||||
lru = lru->next; | |||||
errcode = pt_section_unmap(trash->section); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
free(trash); | |||||
} | |||||
return 0; | |||||
} | |||||
static int pt_iscache_lru_prune(struct pt_image_section_cache *iscache, | |||||
struct pt_iscache_lru_entry **tail) | |||||
{ | |||||
struct pt_iscache_lru_entry *lru, **pnext; | |||||
uint64_t limit, used; | |||||
if (!iscache || !tail) | |||||
return -pte_internal; | |||||
limit = iscache->limit; | |||||
used = 0ull; | |||||
pnext = &iscache->lru; | |||||
for (lru = *pnext; lru; pnext = &lru->next, lru = *pnext) { | |||||
used += lru->size; | |||||
if (used <= limit) | |||||
continue; | |||||
/* The cache got too big; prune it starting from @lru. */ | |||||
iscache->used = used - lru->size; | |||||
*pnext = NULL; | |||||
*tail = lru; | |||||
return 0; | |||||
} | |||||
/* We shouldn't prune the cache unnecessarily. */ | |||||
return -pte_internal; | |||||
} | |||||
/* Add @section to the front of @iscache->lru. | |||||
* | |||||
* Returns a positive integer if we need to prune the cache. | |||||
* Returns zero if we don't need to prune the cache. | |||||
* Returns a negative pt_error_code otherwise. | |||||
*/ | |||||
static int pt_isache_lru_new(struct pt_image_section_cache *iscache, | |||||
struct pt_section *section) | |||||
{ | |||||
struct pt_iscache_lru_entry *lru; | |||||
uint64_t memsize, used, total, limit; | |||||
int errcode; | |||||
if (!iscache) | |||||
return -pte_internal; | |||||
errcode = pt_section_memsize(section, &memsize); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
/* Don't try to add the section if it is too big. We'd prune it again | |||||
* together with all other sections in our cache. | |||||
*/ | |||||
limit = iscache->limit; | |||||
if (limit < memsize) | |||||
return 0; | |||||
errcode = pt_section_map_share(section); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
lru = malloc(sizeof(*lru)); | |||||
if (!lru) { | |||||
(void) pt_section_unmap(section); | |||||
return -pte_nomem; | |||||
} | |||||
lru->section = section; | |||||
lru->size = memsize; | |||||
lru->next = iscache->lru; | |||||
iscache->lru = lru; | |||||
used = iscache->used; | |||||
total = used + memsize; | |||||
if (total < used || total < memsize) | |||||
return -pte_overflow; | |||||
iscache->used = total; | |||||
return (limit < total) ? 1 : 0; | |||||
} | |||||
/* Add or move @section to the front of @iscache->lru. | |||||
* | |||||
* Returns a positive integer if we need to prune the cache. | |||||
* Returns zero if we don't need to prune the cache. | |||||
* Returns a negative pt_error_code otherwise. | |||||
*/ | |||||
static int pt_iscache_lru_add(struct pt_image_section_cache *iscache, | |||||
struct pt_section *section) | |||||
{ | |||||
struct pt_iscache_lru_entry *lru, **pnext; | |||||
if (!iscache) | |||||
return -pte_internal; | |||||
pnext = &iscache->lru; | |||||
for (lru = *pnext; lru; pnext = &lru->next, lru = *pnext) { | |||||
if (lru->section != section) | |||||
continue; | |||||
/* We found it in the cache. Move it to the front. */ | |||||
*pnext = lru->next; | |||||
lru->next = iscache->lru; | |||||
iscache->lru = lru; | |||||
return 0; | |||||
} | |||||
/* We didn't find it in the cache. Add it. */ | |||||
return pt_isache_lru_new(iscache, section); | |||||
} | |||||
/* Remove @section from @iscache->lru. | |||||
* | |||||
* Returns zero on success, a negative pt_error_code otherwise. | |||||
*/ | |||||
static int pt_iscache_lru_remove(struct pt_image_section_cache *iscache, | |||||
const struct pt_section *section) | |||||
{ | |||||
struct pt_iscache_lru_entry *lru, **pnext; | |||||
if (!iscache) | |||||
return -pte_internal; | |||||
pnext = &iscache->lru; | |||||
for (lru = *pnext; lru; pnext = &lru->next, lru = *pnext) { | |||||
if (lru->section != section) | |||||
continue; | |||||
/* We found it in the cache. Remove it. */ | |||||
*pnext = lru->next; | |||||
lru->next = NULL; | |||||
break; | |||||
} | |||||
return pt_iscache_lru_free(lru); | |||||
} | |||||
/* Add or move @section to the front of @iscache->lru and update its size. | |||||
* | |||||
* Returns a positive integer if we need to prune the cache. | |||||
* Returns zero if we don't need to prune the cache. | |||||
* Returns a negative pt_error_code otherwise. | |||||
*/ | |||||
static int pt_iscache_lru_resize(struct pt_image_section_cache *iscache, | |||||
struct pt_section *section, uint64_t memsize) | |||||
{ | |||||
struct pt_iscache_lru_entry *lru; | |||||
uint64_t oldsize, used; | |||||
int status; | |||||
if (!iscache) | |||||
return -pte_internal; | |||||
status = pt_iscache_lru_add(iscache, section); | |||||
if (status < 0) | |||||
return status; | |||||
lru = iscache->lru; | |||||
if (!lru) { | |||||
if (status) | |||||
return -pte_internal; | |||||
return 0; | |||||
} | |||||
/* If @section is cached, it must be first. | |||||
* | |||||
* We may choose not to cache it, though, e.g. if it is too big. | |||||
*/ | |||||
if (lru->section != section) { | |||||
if (iscache->limit < memsize) | |||||
return 0; | |||||
return -pte_internal; | |||||
} | |||||
oldsize = lru->size; | |||||
lru->size = memsize; | |||||
/* If we need to prune anyway, we're done. */ | |||||
if (status) | |||||
return status; | |||||
used = iscache->used; | |||||
used -= oldsize; | |||||
used += memsize; | |||||
iscache->used = used; | |||||
return (iscache->limit < used) ? 1 : 0; | |||||
} | |||||
/* Clear @iscache->lru. | |||||
* | |||||
* Unlike other iscache_lru functions, the caller does not lock @iscache. | |||||
* | |||||
* Return zero on success, a negative pt_error_code otherwise. | |||||
*/ | |||||
static int pt_iscache_lru_clear(struct pt_image_section_cache *iscache) | |||||
{ | |||||
struct pt_iscache_lru_entry *lru; | |||||
int errcode; | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
lru = iscache->lru; | |||||
iscache->lru = NULL; | |||||
iscache->used = 0ull; | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
return pt_iscache_lru_free(lru); | |||||
} | |||||
/* Search @iscache for a partial or exact match of @section loaded at @laddr and | |||||
* return the corresponding index or @iscache->size if no match is found. | |||||
* | |||||
* The caller must lock @iscache. | |||||
* | |||||
* Returns a non-zero index on success, a negative pt_error_code otherwise. | |||||
*/ | |||||
static int | |||||
pt_iscache_find_section_locked(const struct pt_image_section_cache *iscache, | |||||
const char *filename, uint64_t offset, | |||||
uint64_t size, uint64_t laddr) | |||||
{ | |||||
const struct pt_section *section; | |||||
uint16_t idx, end; | |||||
int match; | |||||
if (!iscache || !filename) | |||||
return -pte_internal; | |||||
section = NULL; | |||||
match = end = iscache->size; | |||||
for (idx = 0; idx < end; ++idx) { | |||||
const struct pt_iscache_entry *entry; | |||||
const struct pt_section *sec; | |||||
entry = &iscache->entries[idx]; | |||||
/* We do not zero-initialize the array - a NULL check is | |||||
* pointless. | |||||
*/ | |||||
sec = entry->section; | |||||
/* Avoid redundant match checks. */ | |||||
if (sec != section) { | |||||
const char *sec_filename; | |||||
/* We don't have duplicates. Skip the check. */ | |||||
if (section) | |||||
continue; | |||||
if (offset != pt_section_offset(sec)) | |||||
continue; | |||||
if (size != pt_section_size(sec)) | |||||
continue; | |||||
sec_filename = pt_section_filename(sec); | |||||
if (!sec_filename) | |||||
return -pte_internal; | |||||
if (strcmp(filename, sec_filename) != 0) | |||||
continue; | |||||
/* Use the cached section instead. */ | |||||
section = sec; | |||||
match = idx; | |||||
} | |||||
/* If we didn't continue, @section == @sec and we have a match. | |||||
* | |||||
* If we also find a matching load address, we're done. | |||||
*/ | |||||
if (laddr == entry->laddr) | |||||
return idx; | |||||
} | |||||
return match; | |||||
} | |||||
int pt_iscache_add(struct pt_image_section_cache *iscache, | |||||
struct pt_section *section, uint64_t laddr) | |||||
{ | |||||
const char *filename; | |||||
uint64_t offset, size; | |||||
uint16_t idx; | |||||
int errcode; | |||||
if (!iscache || !section) | |||||
return -pte_internal; | |||||
/* We must have a filename for @section. */ | |||||
filename = pt_section_filename(section); | |||||
if (!filename) | |||||
return -pte_internal; | |||||
offset = pt_section_offset(section); | |||||
size = pt_section_size(section); | |||||
/* Adding a section is slightly complicated by a potential deadlock | |||||
* scenario: | |||||
* | |||||
* - in order to add a section, we need to attach to it, which | |||||
* requires taking the section's attach lock. | |||||
* | |||||
* - if we are already attached to it, we may receive on-map | |||||
* notifications, which will be sent while holding the attach lock | |||||
* and require taking the iscache lock. | |||||
* | |||||
* Hence we can't attach to a section while holding the iscache lock. | |||||
* | |||||
* | |||||
* We therefore attach to @section first and then lock @iscache. | |||||
* | |||||
* This opens a small window where an existing @section may be removed | |||||
* from @iscache and replaced by a new matching section. We would want | |||||
* to share that new section rather than adding a duplicate @section. | |||||
* | |||||
* After locking @iscache, we therefore check for existing matching | |||||
* sections and, if one is found, update @section. This involves | |||||
* detaching from @section and attaching to the existing section. | |||||
* | |||||
* And for this, we will have to temporarily unlock @iscache again. | |||||
*/ | |||||
errcode = pt_section_get(section); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
errcode = pt_section_attach(section, iscache); | |||||
if (errcode < 0) | |||||
goto out_put; | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) | |||||
goto out_detach; | |||||
/* We may need to repeat this step. | |||||
* | |||||
* Typically we don't and this takes only a single iteration. One | |||||
* scenario where we do repeat this is when adding a section with an | |||||
* out-of-bounds size. | |||||
* | |||||
* We will not find a matching section in pt_iscache_add_file() so we | |||||
* create a new section. This will have its size reduced to match the | |||||
* actual file size. | |||||
* | |||||
* For this reduced size, we may now find an existing section, and we | |||||
* will take another trip in the below loop. | |||||
*/ | |||||
for (;;) { | |||||
const struct pt_iscache_entry *entry; | |||||
struct pt_section *sec; | |||||
int match; | |||||
/* Find an existing section matching @section that we'd share | |||||
* rather than adding @section. | |||||
*/ | |||||
match = pt_iscache_find_section_locked(iscache, filename, | |||||
offset, size, laddr); | |||||
if (match < 0) { | |||||
errcode = match; | |||||
goto out_unlock_detach; | |||||
} | |||||
/* We're done if we have not found a matching section. */ | |||||
if (iscache->size <= match) | |||||
break; | |||||
entry = &iscache->entries[match]; | |||||
/* We're also done if we found the same section again. | |||||
* | |||||
* We further check for a perfect match. In that case, we don't | |||||
* need to insert anything, at all. | |||||
*/ | |||||
sec = entry->section; | |||||
if (sec == section) { | |||||
if (entry->laddr == laddr) { | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) | |||||
goto out_detach; | |||||
errcode = pt_section_detach(section, iscache); | |||||
if (errcode < 0) | |||||
goto out_lru; | |||||
errcode = pt_section_put(section); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
return isid_from_index((uint16_t) match); | |||||
} | |||||
break; | |||||
} | |||||
/* We update @section to share the existing @sec. | |||||
* | |||||
* This requires detaching from @section, which, in turn, | |||||
* requires temporarily unlocking @iscache. | |||||
* | |||||
* We further need to remove @section from @iscache->lru. | |||||
*/ | |||||
errcode = pt_section_get(sec); | |||||
if (errcode < 0) | |||||
goto out_unlock_detach; | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) { | |||||
(void) pt_section_put(sec); | |||||
goto out_detach; | |||||
} | |||||
errcode = pt_section_detach(section, iscache); | |||||
if (errcode < 0) { | |||||
(void) pt_section_put(sec); | |||||
goto out_lru; | |||||
} | |||||
errcode = pt_section_attach(sec, iscache); | |||||
if (errcode < 0) { | |||||
(void) pt_section_put(sec); | |||||
goto out_lru; | |||||
} | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) { | |||||
(void) pt_section_put(section); | |||||
/* Complete the swap for cleanup. */ | |||||
section = sec; | |||||
goto out_detach; | |||||
} | |||||
/* We may have received on-map notifications for @section and we | |||||
* may have added @section to @iscache->lru. | |||||
* | |||||
* Since we're still holding a reference to it, no harm has been | |||||
* done. But we need to remove it before we drop our reference. | |||||
*/ | |||||
errcode = pt_iscache_lru_remove(iscache, section); | |||||
if (errcode < 0) { | |||||
(void) pt_section_put(section); | |||||
/* Complete the swap for cleanup. */ | |||||
section = sec; | |||||
goto out_unlock_detach; | |||||
} | |||||
/* Drop the reference to @section. */ | |||||
errcode = pt_section_put(section); | |||||
if (errcode < 0) { | |||||
/* Complete the swap for cleanup. */ | |||||
section = sec; | |||||
goto out_unlock_detach; | |||||
} | |||||
/* Swap sections. | |||||
* | |||||
* We will try again in the next iteration. | |||||
*/ | |||||
section = sec; | |||||
} | |||||
/* Expand the cache, if necessary. */ | |||||
if (iscache->capacity <= iscache->size) { | |||||
/* We must never exceed the capacity. */ | |||||
if (iscache->capacity < iscache->size) { | |||||
errcode = -pte_internal; | |||||
goto out_unlock_detach; | |||||
} | |||||
errcode = pt_iscache_expand(iscache); | |||||
if (errcode < 0) | |||||
goto out_unlock_detach; | |||||
/* Make sure it is big enough, now. */ | |||||
if (iscache->capacity <= iscache->size) { | |||||
errcode = -pte_internal; | |||||
goto out_unlock_detach; | |||||
} | |||||
} | |||||
/* Insert a new entry for @section at @laddr. | |||||
* | |||||
* This hands both attach and reference over to @iscache. We will | |||||
* detach and drop the reference again when the entry is removed. | |||||
*/ | |||||
idx = iscache->size++; | |||||
iscache->entries[idx].section = section; | |||||
iscache->entries[idx].laddr = laddr; | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
return isid_from_index(idx); | |||||
out_unlock_detach: | |||||
(void) pt_iscache_unlock(iscache); | |||||
out_detach: | |||||
(void) pt_section_detach(section, iscache); | |||||
out_lru: | |||||
(void) pt_iscache_lru_clear(iscache); | |||||
out_put: | |||||
(void) pt_section_put(section); | |||||
return errcode; | |||||
} | |||||
int pt_iscache_find(struct pt_image_section_cache *iscache, | |||||
const char *filename, uint64_t offset, uint64_t size, | |||||
uint64_t laddr) | |||||
{ | |||||
int errcode, isid; | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
isid = pt_iscache_find_locked(iscache, filename, offset, size, laddr); | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
return isid; | |||||
} | |||||
int pt_iscache_lookup(struct pt_image_section_cache *iscache, | |||||
struct pt_section **section, uint64_t *laddr, int isid) | |||||
{ | |||||
uint16_t index; | |||||
int errcode, status; | |||||
if (!iscache || !section || !laddr) | |||||
return -pte_internal; | |||||
if (isid <= 0) | |||||
return -pte_bad_image; | |||||
isid -= 1; | |||||
if (isid > UINT16_MAX) | |||||
return -pte_internal; | |||||
index = (uint16_t) isid; | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
if (iscache->size <= index) | |||||
status = -pte_bad_image; | |||||
else { | |||||
const struct pt_iscache_entry *entry; | |||||
entry = &iscache->entries[index]; | |||||
*section = entry->section; | |||||
*laddr = entry->laddr; | |||||
status = pt_section_get(*section); | |||||
} | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
return status; | |||||
} | |||||
int pt_iscache_clear(struct pt_image_section_cache *iscache) | |||||
{ | |||||
struct pt_iscache_lru_entry *lru; | |||||
struct pt_iscache_entry *entries; | |||||
uint16_t idx, end; | |||||
int errcode; | |||||
if (!iscache) | |||||
return -pte_internal; | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
entries = iscache->entries; | |||||
end = iscache->size; | |||||
lru = iscache->lru; | |||||
iscache->entries = NULL; | |||||
iscache->capacity = 0; | |||||
iscache->size = 0; | |||||
iscache->lru = NULL; | |||||
iscache->used = 0ull; | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
errcode = pt_iscache_lru_free(lru); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
for (idx = 0; idx < end; ++idx) { | |||||
struct pt_section *section; | |||||
section = entries[idx].section; | |||||
/* We do not zero-initialize the array - a NULL check is | |||||
* pointless. | |||||
*/ | |||||
errcode = pt_section_detach(section, iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
errcode = pt_section_put(section); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
} | |||||
free(entries); | |||||
return 0; | |||||
} | |||||
struct pt_image_section_cache *pt_iscache_alloc(const char *name) | |||||
{ | |||||
struct pt_image_section_cache *iscache; | |||||
iscache = malloc(sizeof(*iscache)); | |||||
if (iscache) | |||||
pt_iscache_init(iscache, name); | |||||
return iscache; | |||||
} | |||||
void pt_iscache_free(struct pt_image_section_cache *iscache) | |||||
{ | |||||
if (!iscache) | |||||
return; | |||||
pt_iscache_fini(iscache); | |||||
free(iscache); | |||||
} | |||||
int pt_iscache_set_limit(struct pt_image_section_cache *iscache, uint64_t limit) | |||||
{ | |||||
struct pt_iscache_lru_entry *tail; | |||||
int errcode, status; | |||||
if (!iscache) | |||||
return -pte_invalid; | |||||
status = 0; | |||||
tail = NULL; | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
iscache->limit = limit; | |||||
if (limit < iscache->used) | |||||
status = pt_iscache_lru_prune(iscache, &tail); | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0 || status < 0) | |||||
return (status < 0) ? status : errcode; | |||||
return pt_iscache_lru_free(tail); | |||||
} | |||||
const char *pt_iscache_name(const struct pt_image_section_cache *iscache) | |||||
{ | |||||
if (!iscache) | |||||
return NULL; | |||||
return iscache->name; | |||||
} | |||||
int pt_iscache_add_file(struct pt_image_section_cache *iscache, | |||||
const char *filename, uint64_t offset, uint64_t size, | |||||
uint64_t vaddr) | |||||
{ | |||||
struct pt_section *section; | |||||
int errcode, match, isid; | |||||
if (!iscache || !filename) | |||||
return -pte_invalid; | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
match = pt_iscache_find_section_locked(iscache, filename, offset, | |||||
size, vaddr); | |||||
if (match < 0) { | |||||
(void) pt_iscache_unlock(iscache); | |||||
return match; | |||||
} | |||||
/* If we found a perfect match, we will share the existing entry. | |||||
* | |||||
* If we found a section, we need to grab a reference before we unlock. | |||||
* | |||||
* If we didn't find a matching section, we create a new section, which | |||||
* implicitly gives us a reference to it. | |||||
*/ | |||||
if (match < iscache->size) { | |||||
const struct pt_iscache_entry *entry; | |||||
entry = &iscache->entries[match]; | |||||
if (entry->laddr == vaddr) { | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
return isid_from_index((uint16_t) match); | |||||
} | |||||
section = entry->section; | |||||
errcode = pt_section_get(section); | |||||
if (errcode < 0) { | |||||
(void) pt_iscache_unlock(iscache); | |||||
return errcode; | |||||
} | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) { | |||||
(void) pt_section_put(section); | |||||
return errcode; | |||||
} | |||||
} else { | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
section = pt_mk_section(filename, offset, size); | |||||
if (!section) | |||||
return -pte_invalid; | |||||
} | |||||
/* We unlocked @iscache and hold a reference to @section. */ | |||||
isid = pt_iscache_add(iscache, section, vaddr); | |||||
/* We grab a reference when we add the section. Drop the one we | |||||
* obtained before. | |||||
*/ | |||||
errcode = pt_section_put(section); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
return isid; | |||||
} | |||||
int pt_iscache_read(struct pt_image_section_cache *iscache, uint8_t *buffer, | |||||
uint64_t size, int isid, uint64_t vaddr) | |||||
{ | |||||
struct pt_section *section; | |||||
uint64_t laddr; | |||||
int errcode, status; | |||||
if (!iscache || !buffer || !size) | |||||
return -pte_invalid; | |||||
errcode = pt_iscache_lookup(iscache, §ion, &laddr, isid); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
if (vaddr < laddr) { | |||||
(void) pt_section_put(section); | |||||
return -pte_nomap; | |||||
} | |||||
vaddr -= laddr; | |||||
errcode = pt_section_map(section); | |||||
if (errcode < 0) { | |||||
(void) pt_section_put(section); | |||||
return errcode; | |||||
} | |||||
/* We truncate the read if it gets too big. The user is expected to | |||||
* issue further reads for the remaining part. | |||||
*/ | |||||
if (UINT16_MAX < size) | |||||
size = UINT16_MAX; | |||||
status = pt_section_read(section, buffer, (uint16_t) size, vaddr); | |||||
errcode = pt_section_unmap(section); | |||||
if (errcode < 0) { | |||||
(void) pt_section_put(section); | |||||
return errcode; | |||||
} | |||||
errcode = pt_section_put(section); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
return status; | |||||
} | |||||
int pt_iscache_notify_map(struct pt_image_section_cache *iscache, | |||||
struct pt_section *section) | |||||
{ | |||||
struct pt_iscache_lru_entry *tail; | |||||
int errcode, status; | |||||
tail = NULL; | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
status = pt_iscache_lru_add(iscache, section); | |||||
if (status > 0) | |||||
status = pt_iscache_lru_prune(iscache, &tail); | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0 || status < 0) | |||||
return (status < 0) ? status : errcode; | |||||
return pt_iscache_lru_free(tail); | |||||
} | |||||
int pt_iscache_notify_resize(struct pt_image_section_cache *iscache, | |||||
struct pt_section *section, uint64_t memsize) | |||||
{ | |||||
struct pt_iscache_lru_entry *tail; | |||||
int errcode, status; | |||||
tail = NULL; | |||||
errcode = pt_iscache_lock(iscache); | |||||
if (errcode < 0) | |||||
return errcode; | |||||
status = pt_iscache_lru_resize(iscache, section, memsize); | |||||
if (status > 0) | |||||
status = pt_iscache_lru_prune(iscache, &tail); | |||||
errcode = pt_iscache_unlock(iscache); | |||||
if (errcode < 0 || status < 0) | |||||
return (status < 0) ? status : errcode; | |||||
return pt_iscache_lru_free(tail); | |||||
} |