diff --git a/.github/workflows/build-docker-images.yml b/.github/workflows/build-docker-images.yml index 0a27eb8f9f..b3e6cd356b 100644 --- a/.github/workflows/build-docker-images.yml +++ b/.github/workflows/build-docker-images.yml @@ -39,15 +39,16 @@ jobs: with: persist-credentials: false - - name: Pull existing image - run: docker pull ghcr.io/${{ github.repository_owner }}/${{ matrix.IMAGE.TAG_NAME }} || true - if: github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' - - name: Get image name id: image-name run: | ATTEST_IMAGE=$(echo "${{ matrix.IMAGE.TAG_NAME }}" | cut -d ':' -f 1) echo "ATTEST_IMAGE=${ATTEST_IMAGE}" | tee -a "$GITHUB_OUTPUT" + echo "GHCR_OWNER=${GITHUB_REPOSITORY_OWNER,,}" | tee -a "$GITHUB_OUTPUT" + + - name: Pull existing image + run: docker pull "ghcr.io/${{ steps.image-name.outputs.GHCR_OWNER }}/${{ matrix.IMAGE.TAG_NAME }}" || true + if: github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' - name: Setup QEMU uses: docker/setup-qemu-action@v4 @@ -70,15 +71,15 @@ jobs: id: docker-build with: context: ${{ matrix.IMAGE.DOCKERFILE_PATH }} - cache-from: ghcr.io/${{ github.repository_owner }}/${{ matrix.IMAGE.TAG_NAME }} - tags: ghcr.io/${{ github.repository_owner }}/${{ matrix.IMAGE.TAG_NAME }} + cache-from: ghcr.io/${{ steps.image-name.outputs.GHCR_OWNER }}/${{ matrix.IMAGE.TAG_NAME }} + tags: ghcr.io/${{ steps.image-name.outputs.GHCR_OWNER }}/${{ matrix.IMAGE.TAG_NAME }} platforms: ${{ matrix.IMAGE.DOCKER_PLATFORM }} pull: true push: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' }} - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: - subject-name: "ghcr.io/${{ github.repository_owner }}/${{ steps.image-name.outputs.ATTEST_IMAGE }}" + subject-name: "ghcr.io/${{ steps.image-name.outputs.GHCR_OWNER }}/${{ steps.image-name.outputs.ATTEST_IMAGE }}" subject-digest: ${{ steps.docker-build.outputs.digest }} push-to-registry: true if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8a79bafe21..36a8c303b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,7 +55,8 @@ jobs: - name: "Build LATX" run: | - DOCKER_IMAGE="ghcr.io/${{ github.repository_owner }}/${{ matrix.container }}:loong64" + GHCR_OWNER="${GITHUB_REPOSITORY_OWNER,,}" + DOCKER_IMAGE="ghcr.io/${GHCR_OWNER}/${{ matrix.container }}:loong64" DOCKER_CMD="./latxbuild/${{ matrix.type }}.sh -c" docker run --rm \ --platform linux/loong64 \ diff --git a/docs/user/latx-environment.rst b/docs/user/latx-environment.rst index d010571220..933723c934 100644 --- a/docs/user/latx-environment.rst +++ b/docs/user/latx-environment.rst @@ -198,6 +198,12 @@ AOT(预翻译)会保存恢复预翻译信息,某些配置与 AOT 冲突, - 库直通 - - + * - LATX\_KZT\_REGISTRY\_DIAGNOSTICS + - ``-latx-kzt-registry-diagnostics`` + - kzt\_registry\_diagnostics + - KZT registry 诊断输出 + - + - 仅控制 KZT registry 诊断输出,不单独启用 KZT;通常需配合 ``LATX_KZT=2`` 或 ``-latx-kzt 2`` * - LATX\_FPUTAG - ``-latx-fputag`` - option\_fputag diff --git a/linux-user/main.c b/linux-user/main.c index e397b76516..40be8acb8d 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -80,6 +80,7 @@ int box64_pagesize; uintptr_t box64_load_addr = 0; int dlsym_error = 0; int kzt_call_log = 0; +int kzt_registry_diagnostics = 0; int cycle_log = 0; int allow_missing_libs = 1; int box64_nogtk = 0; @@ -635,6 +636,11 @@ static void handle_arg_latx_kzt(const char *arg) { option_kzt = strtol(arg, NULL, 0); } + +static void handle_arg_latx_kzt_registry_diagnostics(const char *arg) +{ + kzt_registry_diagnostics = strtol(arg, NULL, 0); +} #endif static void handle_arg_latx_fputag(const char *arg) @@ -818,6 +824,9 @@ static const struct qemu_argument arg_table[] = { #if defined(CONFIG_LATX_KZT) {"latx-kzt", "LATX_KZT", true, handle_arg_latx_kzt, "", "enable kuzhitong"}, + {"latx-kzt-registry-diagnostics", "LATX_KZT_REGISTRY_DIAGNOSTICS", + true, handle_arg_latx_kzt_registry_diagnostics, + "", "enable KZT guest registry diagnostics"}, #endif {"latx-fputag", "LATX_FPUTAG", true, handle_arg_latx_fputag, "", "enable fputag"}, diff --git a/target/i386/latx/context/box64context.c b/target/i386/latx/context/box64context.c index bd80cc0b4c..e296880a3f 100755 --- a/target/i386/latx/context/box64context.c +++ b/target/i386/latx/context/box64context.c @@ -11,6 +11,9 @@ #include "librarian.h" #include "library.h" #include "wrapper.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_guest_registry.h" +#endif #include box64context_t *NewBox64Context(int argc) @@ -34,6 +37,19 @@ box64context_t *NewBox64Context(int argc) return context; } +#ifdef CONFIG_LATX_KZT +kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context) +{ + if (!context) { + return NULL; + } + if (!context->kzt_guest_registry) { + context->kzt_guest_registry = kzt_guest_registry_init(); + } + return context->kzt_guest_registry; +} +#endif + EXPORTDYN void FreeBox64Context(box64context_t** context) { @@ -45,6 +61,10 @@ void FreeBox64Context(box64context_t** context) box64context_t* ctx = *context; // local copy to do the cleanning +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_destroy(&ctx->kzt_guest_registry); +#endif + if(ctx->local_maplib) FreeLibrarian(&ctx->local_maplib); if(ctx->maplib) diff --git a/target/i386/latx/context/kzt_guest_dynamic.c b/target/i386/latx/context/kzt_guest_dynamic.c new file mode 100644 index 0000000000..70422ddc1a --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dynamic.c @@ -0,0 +1,265 @@ +#include "kzt_guest_dynamic.h" + +#include + +static void kzt_guest_dynamic_field_set( + kzt_guest_dynamic_field_t *field, + uint64_t value, + kzt_guest_dynamic_address_semantics_t semantics) +{ + field->present = 1; + field->value = value; + field->address_semantics = semantics; +} + +static int kzt_guest_dynamic_read_entry( + uintptr_t dynamic_addr, + size_t index, + const kzt_guest_link_map_reader_ops_t *reader_ops, + Elf64_Dyn *entry, + uintptr_t *entry_addr) +{ + uintptr_t offset; + + if (index > UINTPTR_MAX / sizeof(*entry)) { + return -1; + } + + offset = index * sizeof(*entry); + if (dynamic_addr > UINTPTR_MAX - offset) { + return -1; + } + + *entry_addr = dynamic_addr + offset; + return reader_ops->read_memory(*entry_addr, entry, sizeof(*entry), + reader_ops->opaque) == 0 ? 0 : -1; +} + +static int kzt_guest_dynamic_add_needed(kzt_guest_dynamic_view_t *view, + uint64_t offset) +{ + size_t new_count = view->needed_count + 1; + + if (new_count < view->needed_count || + new_count > KZT_GUEST_DYNAMIC_NEEDED_LIMIT) { + return -1; + } + + view->needed_offsets[view->needed_count] = offset; + view->needed_count = new_count; + view->needed_address_semantics = KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET; + return 0; +} + +static void kzt_guest_dynamic_record_unknown_tag( + kzt_guest_dynamic_view_t *view, + int64_t tag, + size_t index) +{ + if (view->unknown_tag_count == 0) { + view->first_unknown_tag = tag; + view->first_unknown_tag_index = index; + } + ++view->unknown_tag_count; +} + +static int kzt_guest_dynamic_record_entry(kzt_guest_dynamic_view_t *view, + const Elf64_Dyn *entry, + size_t index) +{ + uint64_t value = entry->d_un.d_val; + uint64_t ptr = entry->d_un.d_ptr; + + switch (entry->d_tag) { + case DT_NEEDED: + return kzt_guest_dynamic_add_needed(view, value); + case DT_SYMTAB: + kzt_guest_dynamic_field_set(&view->symtab, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_STRTAB: + kzt_guest_dynamic_field_set(&view->strtab, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_SYMENT: + kzt_guest_dynamic_field_set(&view->syment, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_STRSZ: + kzt_guest_dynamic_field_set(&view->strsz, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_HASH: + kzt_guest_dynamic_field_set(&view->hash, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_GNU_HASH: + kzt_guest_dynamic_field_set(&view->gnu_hash, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_VERSYM: + kzt_guest_dynamic_field_set(&view->versym, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_VERNEED: + kzt_guest_dynamic_field_set(&view->verneed, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_VERNEEDNUM: + kzt_guest_dynamic_field_set(&view->verneednum, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_VERDEF: + kzt_guest_dynamic_field_set(&view->verdef, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_VERDEFNUM: + kzt_guest_dynamic_field_set(&view->verdefnum, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_RELA: + kzt_guest_dynamic_field_set(&view->rela, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_RELASZ: + kzt_guest_dynamic_field_set(&view->relasz, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_RELAENT: + kzt_guest_dynamic_field_set(&view->relaent, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_REL: + kzt_guest_dynamic_field_set(&view->rel, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_RELSZ: + kzt_guest_dynamic_field_set(&view->relsz, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_RELENT: + kzt_guest_dynamic_field_set(&view->relent, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_JMPREL: + kzt_guest_dynamic_field_set(&view->jmprel, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + case DT_PLTRELSZ: + kzt_guest_dynamic_field_set(&view->pltrelsz, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_PLTREL: + kzt_guest_dynamic_field_set(&view->pltrel, value, + KZT_GUEST_DYNAMIC_SCALAR); + break; + case DT_PLTGOT: + kzt_guest_dynamic_field_set(&view->pltgot, ptr, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + break; + default: + kzt_guest_dynamic_record_unknown_tag(view, entry->d_tag, index); + break; + } + + return 0; +} + +static void kzt_guest_dynamic_publish_view( + kzt_guest_dynamic_parse_result_t *result, + const kzt_guest_dynamic_view_t *view) +{ + result->status = view->status; + result->entry_count = view->entry_count; + result->scan_limit = view->scan_limit; + result->unknown_tag_count = view->unknown_tag_count; + result->first_unknown_tag = view->first_unknown_tag; + result->first_unknown_tag_index = view->first_unknown_tag_index; + result->view = *view; +} + +int kzt_guest_dynamic_parse( + uintptr_t dynamic_addr, + uintptr_t load_bias, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_dynamic_parse_result_t *result) +{ + kzt_guest_dynamic_view_t view; + size_t i; + + if (!result) { + return -1; + } + + memset(result, 0, sizeof(*result)); + result->status = KZT_GUEST_DYNAMIC_ERROR; + result->error = KZT_GUEST_DYNAMIC_ERROR_INVALID_ARGUMENT; + + if (!dynamic_addr || !reader_ops || !reader_ops->read_memory) { + return -1; + } + + memset(&view, 0, sizeof(view)); + view.dynamic_addr = dynamic_addr; + view.load_bias = load_bias; + view.scan_limit = KZT_GUEST_DYNAMIC_SCAN_LIMIT; + + for (i = 0; i < KZT_GUEST_DYNAMIC_SCAN_LIMIT; ++i) { + Elf64_Dyn entry; + uintptr_t entry_addr = 0; + + if (kzt_guest_dynamic_read_entry(dynamic_addr, i, reader_ops, + &entry, &entry_addr) != 0) { + view.status = KZT_GUEST_DYNAMIC_READ_ERROR; + view.entry_count = i; + result->read_error_addr = entry_addr; + result->error = KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE; + kzt_guest_dynamic_publish_view(result, &view); + return 0; + } + + if (entry.d_tag == DT_NULL) { + view.status = KZT_GUEST_DYNAMIC_COMPLETE; + view.entry_count = i; + view.has_null = 1; + result->error = KZT_GUEST_DYNAMIC_ERROR_NONE; + kzt_guest_dynamic_publish_view(result, &view); + return 0; + } + + if (kzt_guest_dynamic_record_entry(&view, &entry, i) != 0) { + view.status = KZT_GUEST_DYNAMIC_ERROR; + view.entry_count = i; + result->status = KZT_GUEST_DYNAMIC_ERROR; + result->error = KZT_GUEST_DYNAMIC_ERROR_TOO_MANY_NEEDED; + kzt_guest_dynamic_publish_view(result, &view); + return 0; + } + } + + view.status = KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + view.entry_count = KZT_GUEST_DYNAMIC_SCAN_LIMIT; + result->error = KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED; + kzt_guest_dynamic_publish_view(result, &view); + return 0; +} + +void kzt_guest_dynamic_view_destroy(kzt_guest_dynamic_view_t *view) +{ + if (!view) { + return; + } + + memset(view, 0, sizeof(*view)); +} + +void kzt_guest_dynamic_parse_result_clear( + kzt_guest_dynamic_parse_result_t *result) +{ + if (!result) { + return; + } + + kzt_guest_dynamic_view_destroy(&result->view); + memset(result, 0, sizeof(*result)); +} diff --git a/target/i386/latx/context/kzt_guest_dynamic_diagnostics.c b/target/i386/latx/context/kzt_guest_dynamic_diagnostics.c new file mode 100644 index 0000000000..b2db896b98 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_dynamic_diagnostics.c @@ -0,0 +1,499 @@ +#include "kzt_guest_dynamic_diagnostics.h" + +#include +#include +#include + +typedef struct kzt_guest_dynamic_field_spec { + const char *name; + size_t offset; +} kzt_guest_dynamic_field_spec_t; + +static int kzt_guest_dynamic_status_is_blocking( + kzt_guest_dynamic_status_t status) +{ + return status == KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL || + status == KZT_GUEST_DYNAMIC_READ_ERROR || + status == KZT_GUEST_DYNAMIC_ERROR; +} + +static kzt_guest_dynamic_diagnostic_match_t kzt_guest_dynamic_compare_size( + size_t old_value, + size_t new_value) +{ + return old_value == new_value ? KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED : + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; +} + +static kzt_guest_dynamic_diagnostic_match_t kzt_guest_dynamic_compare_status( + kzt_guest_dynamic_status_t old_status, + kzt_guest_dynamic_status_t new_status) +{ + return old_status == new_status ? KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED : + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; +} + +static void kzt_guest_dynamic_count_field_match( + kzt_guest_dynamic_diagnostic_report_t *report, + kzt_guest_dynamic_diagnostic_match_t match) +{ + switch (match) { + case KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED: + ++report->matched_count; + break; + case KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_OLD: + ++report->missing_old_count; + break; + case KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW: + ++report->missing_new_count; + break; + case KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH: + ++report->mismatch_count; + break; + } +} + +static void kzt_guest_dynamic_add_field_report( + kzt_guest_dynamic_diagnostic_report_t *report, + const kzt_guest_dynamic_diagnostic_field_t *field) +{ + if (report->field_count >= KZT_GUEST_DYNAMIC_DIAGNOSTIC_FIELD_LIMIT) { + return; + } + + report->fields[report->field_count++] = *field; + kzt_guest_dynamic_count_field_match(report, field->match); +} + +static kzt_guest_dynamic_diagnostic_match_t kzt_guest_dynamic_compare_field( + const kzt_guest_dynamic_field_t *old_field, + const kzt_guest_dynamic_field_t *new_field) +{ + if (!old_field->present && !new_field->present) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + } + if (!old_field->present) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_OLD; + } + if (!new_field->present) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW; + } + if (old_field->value != new_field->value || + old_field->address_semantics != new_field->address_semantics) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; + } + + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; +} + +static const kzt_guest_dynamic_field_t *kzt_guest_dynamic_field_at( + const kzt_guest_dynamic_view_t *view, + const kzt_guest_dynamic_field_spec_t *spec) +{ + return (const kzt_guest_dynamic_field_t *)((const char *)view + + spec->offset); +} + +static void kzt_guest_dynamic_compare_named_field( + kzt_guest_dynamic_diagnostic_report_t *report, + const kzt_guest_dynamic_view_t *old_view, + const kzt_guest_dynamic_view_t *new_view, + const kzt_guest_dynamic_field_spec_t *spec) +{ + const kzt_guest_dynamic_field_t *old_field = + kzt_guest_dynamic_field_at(old_view, spec); + const kzt_guest_dynamic_field_t *new_field = + kzt_guest_dynamic_field_at(new_view, spec); + kzt_guest_dynamic_diagnostic_field_t field = { + .name = spec->name, + .match = kzt_guest_dynamic_compare_field(old_field, new_field), + .old_present = old_field->present, + .old_value = old_field->value, + .old_address_semantics = old_field->address_semantics, + .new_present = new_field->present, + .new_value = new_field->value, + .new_address_semantics = new_field->address_semantics, + }; + + kzt_guest_dynamic_add_field_report(report, &field); +} + +static int kzt_guest_dynamic_needed_offsets_equal( + const kzt_guest_dynamic_view_t *old_view, + const kzt_guest_dynamic_view_t *new_view) +{ + size_t i; + + if (old_view->needed_count != new_view->needed_count || + old_view->needed_address_semantics != + new_view->needed_address_semantics) { + return 0; + } + + for (i = 0; i < old_view->needed_count; ++i) { + if (old_view->needed_offsets[i] != new_view->needed_offsets[i]) { + return 0; + } + } + + return 1; +} + +static void kzt_guest_dynamic_compare_needed_offsets( + kzt_guest_dynamic_diagnostic_report_t *report, + const kzt_guest_dynamic_view_t *old_view, + const kzt_guest_dynamic_view_t *new_view) +{ + int old_present = old_view->needed_count > 0; + int new_present = new_view->needed_count > 0; + kzt_guest_dynamic_diagnostic_field_t field = { + .name = "needed_offsets", + .old_present = old_present, + .old_value = old_present ? old_view->needed_offsets[0] : 0, + .old_address_semantics = old_view->needed_address_semantics, + .old_count = old_view->needed_count, + .new_present = new_present, + .new_value = new_present ? new_view->needed_offsets[0] : 0, + .new_address_semantics = new_view->needed_address_semantics, + .new_count = new_view->needed_count, + }; + + if (!old_present && !new_present) { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + } else if (!old_present) { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_OLD; + } else if (!new_present) { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW; + } else if (!kzt_guest_dynamic_needed_offsets_equal(old_view, new_view)) { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; + } else { + field.match = KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + } + + kzt_guest_dynamic_add_field_report(report, &field); +} + +static kzt_guest_dynamic_diagnostic_match_t kzt_guest_dynamic_compare_unknown( + const kzt_guest_dynamic_parse_result_t *old_result, + const kzt_guest_dynamic_parse_result_t *new_result) +{ + if (old_result->unknown_tag_count != new_result->unknown_tag_count) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; + } + + if (old_result->unknown_tag_count == 0) { + return KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + } + + return old_result->first_unknown_tag == new_result->first_unknown_tag && + old_result->first_unknown_tag_index == + new_result->first_unknown_tag_index ? + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED : + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH; +} + +static size_t kzt_guest_dynamic_count_summary_differences( + const kzt_guest_dynamic_diagnostic_report_t *report) +{ + size_t count = report->missing_old_count + report->missing_new_count + + report->mismatch_count; + + if (report->status_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + ++count; + } + if (report->entry_count_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + ++count; + } + if (report->unknown_tags_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + ++count; + } + + return count; +} + +static void kzt_guest_dynamic_summary_set_field( + kzt_guest_dynamic_diagnostic_summary_t *summary, + const kzt_guest_dynamic_diagnostic_field_t *field) +{ + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_FIELD; + summary->first_difference_name = field->name; + summary->first_difference_match = field->match; + summary->first_old_present = field->old_present; + summary->first_new_present = field->new_present; + summary->first_old_value = field->old_value; + summary->first_new_value = field->new_value; + summary->first_old_count = field->old_count; + summary->first_new_count = field->new_count; +} + +static void kzt_guest_dynamic_summary_set_status( + kzt_guest_dynamic_diagnostic_summary_t *summary, + const kzt_guest_dynamic_diagnostic_report_t *report) +{ + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_STATUS; + summary->first_difference_name = "status"; + summary->first_difference_match = report->status_match; + summary->first_old_present = 1; + summary->first_new_present = 1; + summary->first_old_value = report->old_status; + summary->first_new_value = report->new_status; +} + +static void kzt_guest_dynamic_summary_set_entry_count( + kzt_guest_dynamic_diagnostic_summary_t *summary, + const kzt_guest_dynamic_diagnostic_report_t *report) +{ + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_ENTRY_COUNT; + summary->first_difference_name = "entry_count"; + summary->first_difference_match = report->entry_count_match; + summary->first_old_present = 1; + summary->first_new_present = 1; + summary->first_old_count = report->old_entry_count; + summary->first_new_count = report->new_entry_count; +} + +static void kzt_guest_dynamic_summary_set_unknown_tags( + kzt_guest_dynamic_diagnostic_summary_t *summary, + const kzt_guest_dynamic_diagnostic_report_t *report) +{ + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_UNKNOWN_TAGS; + summary->first_difference_name = "unknown_tags"; + summary->first_difference_match = report->unknown_tags_match; + summary->first_old_present = report->old_unknown_tag_count > 0; + summary->first_new_present = report->new_unknown_tag_count > 0; + summary->first_old_count = report->old_unknown_tag_count; + summary->first_new_count = report->new_unknown_tag_count; + summary->first_old_tag = report->old_first_unknown_tag; + summary->first_new_tag = report->new_first_unknown_tag; + summary->first_old_tag_index = report->old_first_unknown_tag_index; + summary->first_new_tag_index = report->new_first_unknown_tag_index; +} + +int kzt_guest_dynamic_diagnostics_summarize( + const kzt_guest_dynamic_diagnostic_report_t *report, + uintptr_t link_map_addr, + unsigned long generation, + kzt_guest_dynamic_diagnostic_summary_t *summary) +{ + size_t i; + + if (!report || !summary) { + return -1; + } + + memset(summary, 0, sizeof(*summary)); + summary->link_map_addr = link_map_addr; + summary->generation = generation; + summary->matched = report->difference_count == 0; + summary->blocking = report->blocking_count > 0; + summary->difference_count = report->difference_count; + summary->blocking_count = report->blocking_count; + summary->old_status = report->old_status; + summary->new_status = report->new_status; + summary->old_entry_count = report->old_entry_count; + summary->new_entry_count = report->new_entry_count; + summary->old_unknown_tag_count = report->old_unknown_tag_count; + summary->new_unknown_tag_count = report->new_unknown_tag_count; + summary->old_first_unknown_tag = report->old_first_unknown_tag; + summary->new_first_unknown_tag = report->new_first_unknown_tag; + summary->old_first_unknown_tag_index = + report->old_first_unknown_tag_index; + summary->new_first_unknown_tag_index = + report->new_first_unknown_tag_index; + summary->first_difference_kind = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_NONE; + summary->first_difference_name = "none"; + summary->first_difference_match = + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED; + + if (report->status_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED || + report->blocking_count > 0) { + kzt_guest_dynamic_summary_set_status(summary, report); + return 0; + } + + if (report->entry_count_match != KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + kzt_guest_dynamic_summary_set_entry_count(summary, report); + return 0; + } + + if (report->unknown_tags_match != + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + kzt_guest_dynamic_summary_set_unknown_tags(summary, report); + return 0; + } + + for (i = 0; i < report->field_count; ++i) { + if (report->fields[i].match != + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED) { + kzt_guest_dynamic_summary_set_field(summary, + &report->fields[i]); + return 0; + } + } + + return 0; +} + +int kzt_guest_dynamic_diagnostics_format_summary( + const kzt_guest_dynamic_diagnostic_summary_t *summary, + char *buffer, + size_t buffer_size) +{ + int written; + const char *first_name; + + if (!summary || !buffer || buffer_size == 0) { + return -1; + } + + first_name = summary->first_difference_name ? + summary->first_difference_name : "none"; + written = snprintf( + buffer, buffer_size, + "kzt_guest_dynamic_compare link_map=0x%lx generation=%lu " + "matched=%d blocking=%d differences=%lu blocking_count=%lu " + "first=%s kind=%d match=%d old_present=%d new_present=%d " + "old_value=0x%llx new_value=0x%llx old_count=%lu new_count=%lu " + "old_tag=%lld new_tag=%lld old_tag_index=%lu new_tag_index=%lu " + "old_status=%d new_status=%d old_entries=%lu new_entries=%lu " + "old_unknown_tags=%lu new_unknown_tags=%lu", + (unsigned long)summary->link_map_addr, summary->generation, + summary->matched, summary->blocking, + (unsigned long)summary->difference_count, + (unsigned long)summary->blocking_count, first_name, + summary->first_difference_kind, summary->first_difference_match, + summary->first_old_present, summary->first_new_present, + (unsigned long long)summary->first_old_value, + (unsigned long long)summary->first_new_value, + (unsigned long)summary->first_old_count, + (unsigned long)summary->first_new_count, + (long long)summary->first_old_tag, + (long long)summary->first_new_tag, + (unsigned long)summary->first_old_tag_index, + (unsigned long)summary->first_new_tag_index, + summary->old_status, summary->new_status, + (unsigned long)summary->old_entry_count, + (unsigned long)summary->new_entry_count, + (unsigned long)summary->old_unknown_tag_count, + (unsigned long)summary->new_unknown_tag_count); + + if (written < 0 || (size_t)written >= buffer_size) { + return -1; + } + + return 0; +} + +int kzt_guest_dynamic_diagnostics_compare( + const kzt_guest_dynamic_parse_result_t *old_result, + const kzt_guest_dynamic_parse_result_t *new_result, + kzt_guest_dynamic_diagnostic_report_t *report) +{ + const kzt_guest_dynamic_view_t *old_view; + const kzt_guest_dynamic_view_t *new_view; + const kzt_guest_dynamic_field_spec_t fields[] = { +#define KZT_DYNAMIC_FIELD(name) \ + { #name, offsetof(kzt_guest_dynamic_view_t, name) } + KZT_DYNAMIC_FIELD(symtab), + KZT_DYNAMIC_FIELD(strtab), + KZT_DYNAMIC_FIELD(syment), + KZT_DYNAMIC_FIELD(strsz), + KZT_DYNAMIC_FIELD(hash), + KZT_DYNAMIC_FIELD(gnu_hash), + KZT_DYNAMIC_FIELD(versym), + KZT_DYNAMIC_FIELD(verneed), + KZT_DYNAMIC_FIELD(verneednum), + KZT_DYNAMIC_FIELD(verdef), + KZT_DYNAMIC_FIELD(verdefnum), + KZT_DYNAMIC_FIELD(rela), + KZT_DYNAMIC_FIELD(relasz), + KZT_DYNAMIC_FIELD(relaent), + KZT_DYNAMIC_FIELD(rel), + KZT_DYNAMIC_FIELD(relsz), + KZT_DYNAMIC_FIELD(relent), + KZT_DYNAMIC_FIELD(jmprel), + KZT_DYNAMIC_FIELD(pltrelsz), + KZT_DYNAMIC_FIELD(pltrel), + KZT_DYNAMIC_FIELD(pltgot), +#undef KZT_DYNAMIC_FIELD + }; + size_t i; + + if (!old_result || !new_result || !report) { + return -1; + } + + memset(report, 0, sizeof(*report)); + + old_view = &old_result->view; + new_view = &new_result->view; + + report->old_status = old_result->status; + report->new_status = new_result->status; + report->old_error = old_result->error; + report->new_error = new_result->error; + report->old_read_error_addr = old_result->read_error_addr; + report->new_read_error_addr = new_result->read_error_addr; + report->old_entry_count = old_result->entry_count; + report->new_entry_count = new_result->entry_count; + report->old_unknown_tag_count = old_result->unknown_tag_count; + report->new_unknown_tag_count = new_result->unknown_tag_count; + report->old_first_unknown_tag = old_result->first_unknown_tag; + report->new_first_unknown_tag = new_result->first_unknown_tag; + report->old_first_unknown_tag_index = old_result->first_unknown_tag_index; + report->new_first_unknown_tag_index = new_result->first_unknown_tag_index; + report->old_truncated = old_result->status == + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + report->new_truncated = new_result->status == + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + report->old_read_error = old_result->status == KZT_GUEST_DYNAMIC_READ_ERROR; + report->new_read_error = new_result->status == KZT_GUEST_DYNAMIC_READ_ERROR; + report->status_match = kzt_guest_dynamic_compare_status(old_result->status, + new_result->status); + report->entry_count_match = kzt_guest_dynamic_compare_size( + old_result->entry_count, new_result->entry_count); + report->unknown_tags_match = kzt_guest_dynamic_compare_unknown(old_result, + new_result); + + if (kzt_guest_dynamic_status_is_blocking(old_result->status)) { + ++report->blocking_count; + } + if (kzt_guest_dynamic_status_is_blocking(new_result->status)) { + ++report->blocking_count; + } + + for (i = 0; i < sizeof(fields) / sizeof(fields[0]); ++i) { + kzt_guest_dynamic_compare_named_field(report, old_view, new_view, + &fields[i]); + } + kzt_guest_dynamic_compare_needed_offsets(report, old_view, new_view); + report->difference_count = kzt_guest_dynamic_count_summary_differences( + report); + + return 0; +} + +const kzt_guest_dynamic_diagnostic_field_t * +kzt_guest_dynamic_diagnostic_find_field( + const kzt_guest_dynamic_diagnostic_report_t *report, + const char *name) +{ + size_t i; + + if (!report || !name) { + return NULL; + } + + for (i = 0; i < report->field_count; ++i) { + if (!strcmp(report->fields[i].name, name)) { + return &report->fields[i]; + } + } + + return NULL; +} diff --git a/target/i386/latx/context/kzt_guest_link_map_reader.c b/target/i386/latx/context/kzt_guest_link_map_reader.c new file mode 100644 index 0000000000..e9a481f0f4 --- /dev/null +++ b/target/i386/latx/context/kzt_guest_link_map_reader.c @@ -0,0 +1,271 @@ +#include +#include +#include +#include + +#include "kzt_guest_link_map_reader.h" +#include "box64context.h" + +#ifdef KZT_GUEST_LINK_MAP_READER_TEST +static long test_alloc_failure_after = -1; + +void kzt_guest_link_map_reader_test_set_alloc_failure_after(long allocations) +{ + test_alloc_failure_after = allocations; +} +#endif + +static void *kzt_link_map_reader_malloc(size_t size) +{ +#ifdef KZT_GUEST_LINK_MAP_READER_TEST + if (test_alloc_failure_after == 0) { + return NULL; + } + if (test_alloc_failure_after > 0) { + --test_alloc_failure_after; + } +#endif + return malloc(size); +} + +static void kzt_link_map_reader_free(void *ptr) +{ + free(ptr); +} + +static void kzt_guest_scalar_set_unknown(kzt_guest_scalar_field_t *field) +{ + field->value = 0; + field->status = KZT_GUEST_FIELD_UNKNOWN; +} + +static void kzt_guest_string_set_unknown(kzt_guest_string_field_t *field) +{ + field->value = NULL; + field->status = KZT_GUEST_FIELD_UNKNOWN; +} + +static void kzt_guest_observation_init(kzt_guest_object_observation_t *observation) +{ + memset(observation, 0, sizeof(*observation)); + kzt_guest_scalar_set_unknown(&observation->load_bias); + kzt_guest_scalar_set_unknown(&observation->dynamic_addr); + kzt_guest_scalar_set_unknown(&observation->map_start); + kzt_guest_scalar_set_unknown(&observation->map_end); + kzt_guest_scalar_set_unknown(&observation->namespace_id); + kzt_guest_string_set_unknown(&observation->path); + observation->soname.value = NULL; + observation->soname.status = KZT_GUEST_FIELD_NOT_PARSED; + observation->dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED; +} + +static int kzt_guest_read_memory(uintptr_t guest_addr, + void *dst, + size_t size, + const kzt_guest_link_map_reader_ops_t *ops) +{ + if (!ops || !ops->read_memory) { + return -1; + } + return ops->read_memory(guest_addr, dst, size, ops->opaque) == 0 ? 0 : -1; +} + +static int kzt_guest_field_addr(uintptr_t base, size_t offset, uintptr_t *addr) +{ + if (base > UINTPTR_MAX - offset) { + return -1; + } + + *addr = base + offset; + return 0; +} + +static int kzt_guest_read_uintptr_field( + uintptr_t base, + size_t offset, + size_t field_size, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *value) +{ + uintptr_t addr; + uint64_t raw = 0; + + if (field_size > sizeof(raw) || + kzt_guest_field_addr(base, offset, &addr) != 0) { + return -1; + } + + if (kzt_guest_read_memory(addr, &raw, field_size, ops) != 0) { + return -1; + } + + *value = (uintptr_t)raw; + return 0; +} + +static void kzt_guest_read_scalar_field( + uintptr_t base, + size_t offset, + size_t field_size, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_scalar_field_t *field) +{ + uintptr_t value = 0; + + field->value = 0; + if (kzt_guest_read_uintptr_field(base, offset, field_size, ops, &value) != 0) { + field->status = KZT_GUEST_FIELD_READ_ERROR; + return; + } + + field->value = value; + field->status = KZT_GUEST_FIELD_OK; +} + +static int kzt_guest_read_link_map_pointer( + uintptr_t link_map_addr, + size_t offset, + size_t field_size, + const kzt_guest_link_map_reader_ops_t *ops, + uintptr_t *value) +{ + *value = 0; + return kzt_guest_read_uintptr_field(link_map_addr, offset, field_size, + ops, value); +} + +int kzt_guest_link_map_read_name_snapshot( + uintptr_t guest_name_addr, + const kzt_guest_link_map_reader_ops_t *ops, + size_t max_len, + kzt_guest_string_field_t *name) +{ + char *snapshot; + size_t i; + + if (!name) { + return -1; + } + + kzt_guest_string_set_unknown(name); + if (!guest_name_addr) { + return 0; + } + + if (max_len == 0) { + name->status = KZT_GUEST_FIELD_TRUNCATED; + return 0; + } + + snapshot = kzt_link_map_reader_malloc(max_len + 1); + if (!snapshot) { + name->status = KZT_GUEST_FIELD_READ_ERROR; + return 0; + } + + for (i = 0; i < max_len; ++i) { + if (kzt_guest_read_memory(guest_name_addr + i, &snapshot[i], 1, ops) != 0) { + kzt_link_map_reader_free(snapshot); + name->status = KZT_GUEST_FIELD_READ_ERROR; + return 0; + } + + if (snapshot[i] == '\0') { + name->value = snapshot; + name->status = KZT_GUEST_FIELD_OK; + return 0; + } + } + + snapshot[max_len] = '\0'; + name->value = snapshot; + name->status = KZT_GUEST_FIELD_TRUNCATED; + return 0; +} + +int kzt_guest_link_map_read_observation( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_object_observation_t *observation) +{ + uintptr_t guest_name_addr = 0; + uintptr_t load_bias = 0; + int name_ptr_read; + + if (!observation) { + return -1; + } + + kzt_guest_observation_init(observation); + + if (!link_map_addr || !ops || !ops->read_memory) { + return -1; + } + + if (kzt_guest_read_uintptr_field( + link_map_addr, offsetof(struct link_map_x64, l_addr), + sizeof(((struct link_map_x64 *)0)->l_addr), ops, + &load_bias) != 0) { + return -1; + } + + observation->link_map_addr = link_map_addr; + observation->load_bias.value = load_bias; + observation->load_bias.status = KZT_GUEST_FIELD_OK; + kzt_guest_read_scalar_field( + link_map_addr, offsetof(struct link_map_x64, l_ld), + sizeof(((struct link_map_x64 *)0)->l_ld), ops, + &observation->dynamic_addr); + kzt_guest_read_scalar_field( + link_map_addr, offsetof(struct link_map_x64, l_map_start), + sizeof(((struct link_map_x64 *)0)->l_map_start), ops, + &observation->map_start); + kzt_guest_read_scalar_field( + link_map_addr, offsetof(struct link_map_x64, l_map_end), + sizeof(((struct link_map_x64 *)0)->l_map_end), ops, + &observation->map_end); + kzt_guest_read_scalar_field( + link_map_addr, offsetof(struct link_map_x64, l_ns), + sizeof(((struct link_map_x64 *)0)->l_ns), ops, + &observation->namespace_id); + + name_ptr_read = kzt_guest_read_link_map_pointer( + link_map_addr, offsetof(struct link_map_x64, l_name), + sizeof(((struct link_map_x64 *)0)->l_name), ops, + &guest_name_addr); + if (name_ptr_read != 0) { + observation->path.value = NULL; + observation->path.status = KZT_GUEST_FIELD_READ_ERROR; + return 0; + } + + if (kzt_guest_link_map_read_name_snapshot( + guest_name_addr, ops, KZT_GUEST_LINK_MAP_NAME_LIMIT, + &observation->path) != 0) { + return -1; + } + + return 0; +} + +void kzt_guest_link_map_observation_clear( + kzt_guest_object_observation_t *observation) +{ + if (!observation) { + return; + } + + kzt_guest_link_map_string_clear(&observation->path); + kzt_guest_link_map_string_clear(&observation->soname); + kzt_guest_observation_init(observation); +} + +void kzt_guest_link_map_string_clear(kzt_guest_string_field_t *field) +{ + if (!field) { + return; + } + + kzt_link_map_reader_free((void *)field->value); + kzt_guest_string_set_unknown(field); +} diff --git a/target/i386/latx/context/kzt_guest_registry.c b/target/i386/latx/context/kzt_guest_registry.c new file mode 100644 index 0000000000..21fa2c35fb --- /dev/null +++ b/target/i386/latx/context/kzt_guest_registry.c @@ -0,0 +1,1122 @@ +#include +#include +#include +#include +#include +#include + +#include "kzt_guest_registry.h" + +#define KZT_GUEST_REGISTRY_INITIAL_CAPACITY 8 + +struct kzt_guest_registry { + pthread_mutex_t lock; + int lock_ready; + int disabled; + kzt_guest_object_snapshot_t *objects; + size_t count; + size_t capacity; + unsigned long next_generation; + kzt_guest_registry_diagnostics_t diagnostics; + kzt_guest_registry_diagnostic_config_t diagnostic_config; + kzt_guest_registry_event_summary_t diagnostic_events[ + KZT_GUEST_REGISTRY_RESULT_COUNT]; +}; + +#ifdef KZT_GUEST_REGISTRY_TEST +static long test_alloc_failure_after = -1; +static long test_dynamic_commit_failure_after = -1; + +void kzt_guest_registry_test_set_alloc_failure_after(long allocations) +{ + test_alloc_failure_after = allocations; +} + +void kzt_guest_registry_test_set_dynamic_commit_failure_after(long commits) +{ + test_dynamic_commit_failure_after = commits; +} + +static int kzt_registry_test_should_fail_dynamic_commit(void) +{ + if (test_dynamic_commit_failure_after == 0) { + return 1; + } + if (test_dynamic_commit_failure_after > 0) { + --test_dynamic_commit_failure_after; + } + return 0; +} +#endif + +static void *kzt_registry_calloc(size_t count, size_t size) +{ +#ifdef KZT_GUEST_REGISTRY_TEST + if (test_alloc_failure_after == 0) { + return NULL; + } + if (test_alloc_failure_after > 0) { + --test_alloc_failure_after; + } +#endif + return calloc(count, size); +} + +static void kzt_registry_free(void *ptr) +{ + free(ptr); +} + +static char *kzt_registry_strdup(const char *value) +{ + size_t len; + char *copy; + + if (!value) { + value = ""; + } + + len = strlen(value); + copy = kzt_registry_calloc(len + 1, 1); + if (!copy) { + return NULL; + } + + memcpy(copy, value, len); + return copy; +} + +static int kzt_field_is_reliable(kzt_guest_field_status_t status) +{ + return status == KZT_GUEST_FIELD_OK; +} + +static int kzt_string_status_has_snapshot(kzt_guest_field_status_t status) +{ + return status == KZT_GUEST_FIELD_OK || + status == KZT_GUEST_FIELD_TRUNCATED; +} + +static void kzt_free_string_field(kzt_guest_string_field_t *field) +{ + if (!field) { + return; + } + + kzt_registry_free((void *)field->value); + field->value = NULL; +} + +static int kzt_copy_string_field(kzt_guest_string_field_t *dst, + const kzt_guest_string_field_t *src) +{ + dst->status = src->status; + dst->value = NULL; + + if (!kzt_string_status_has_snapshot(src->status)) { + return 0; + } + + dst->value = kzt_registry_strdup(src->value); + return dst->value ? 0 : -1; +} + +static int kzt_dynamic_field_equal( + const kzt_guest_dynamic_field_t *left, + const kzt_guest_dynamic_field_t *right) +{ + return left->present == right->present && + left->value == right->value && + left->address_semantics == right->address_semantics; +} + +static int kzt_dynamic_needed_equal( + const kzt_guest_dynamic_view_t *left, + const kzt_guest_dynamic_view_t *right) +{ + size_t i; + + if (left->needed_count != right->needed_count) { + return 0; + } + + if (left->needed_count > 0 && + left->needed_address_semantics != right->needed_address_semantics) { + return 0; + } + + for (i = 0; i < left->needed_count; ++i) { + if (left->needed_offsets[i] != right->needed_offsets[i]) { + return 0; + } + } + + return 1; +} + +static int kzt_dynamic_view_equal( + const kzt_guest_dynamic_view_t *left, + const kzt_guest_dynamic_view_t *right) +{ + return left->dynamic_addr == right->dynamic_addr && + left->load_bias == right->load_bias && + left->status == right->status && + left->entry_count == right->entry_count && + left->has_null == right->has_null && + left->scan_limit == right->scan_limit && + left->unknown_tag_count == right->unknown_tag_count && + left->first_unknown_tag == right->first_unknown_tag && + left->first_unknown_tag_index == right->first_unknown_tag_index && + kzt_dynamic_field_equal(&left->symtab, &right->symtab) && + kzt_dynamic_field_equal(&left->strtab, &right->strtab) && + kzt_dynamic_field_equal(&left->syment, &right->syment) && + kzt_dynamic_field_equal(&left->strsz, &right->strsz) && + kzt_dynamic_field_equal(&left->hash, &right->hash) && + kzt_dynamic_field_equal(&left->gnu_hash, &right->gnu_hash) && + kzt_dynamic_field_equal(&left->versym, &right->versym) && + kzt_dynamic_field_equal(&left->verneed, &right->verneed) && + kzt_dynamic_field_equal(&left->verneednum, &right->verneednum) && + kzt_dynamic_field_equal(&left->verdef, &right->verdef) && + kzt_dynamic_field_equal(&left->verdefnum, &right->verdefnum) && + kzt_dynamic_field_equal(&left->rela, &right->rela) && + kzt_dynamic_field_equal(&left->relasz, &right->relasz) && + kzt_dynamic_field_equal(&left->relaent, &right->relaent) && + kzt_dynamic_field_equal(&left->rel, &right->rel) && + kzt_dynamic_field_equal(&left->relsz, &right->relsz) && + kzt_dynamic_field_equal(&left->relent, &right->relent) && + kzt_dynamic_field_equal(&left->jmprel, &right->jmprel) && + kzt_dynamic_field_equal(&left->pltrelsz, &right->pltrelsz) && + kzt_dynamic_field_equal(&left->pltrel, &right->pltrel) && + kzt_dynamic_field_equal(&left->pltgot, &right->pltgot) && + kzt_dynamic_needed_equal(left, right); +} + +static kzt_guest_field_status_t kzt_dynamic_view_field_status( + const kzt_guest_dynamic_view_t *view) +{ + switch (view->status) { + case KZT_GUEST_DYNAMIC_COMPLETE: + return KZT_GUEST_FIELD_OK; + case KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL: + return KZT_GUEST_FIELD_TRUNCATED; + case KZT_GUEST_DYNAMIC_READ_ERROR: + return KZT_GUEST_FIELD_READ_ERROR; + case KZT_GUEST_DYNAMIC_ERROR: + return KZT_GUEST_FIELD_READ_ERROR; + } + + return KZT_GUEST_FIELD_UNKNOWN; +} + +static void kzt_free_snapshot_strings(kzt_guest_object_snapshot_t *snapshot) +{ + if (!snapshot) { + return; + } + + kzt_free_string_field(&snapshot->path); + kzt_free_string_field(&snapshot->soname); + memset(&snapshot->dynamic_view, 0, sizeof(snapshot->dynamic_view)); +} + +static void kzt_free_snapshot_array(kzt_guest_object_snapshot_t *objects, + size_t count) +{ + size_t i; + + if (!objects) { + return; + } + + for (i = 0; i < count; ++i) { + kzt_free_snapshot_strings(&objects[i]); + } + kzt_registry_free(objects); +} + +static int kzt_copy_snapshot(kzt_guest_object_snapshot_t *dst, + const kzt_guest_object_snapshot_t *src) +{ + *dst = *src; + dst->path.value = NULL; + dst->soname.value = NULL; + + if (kzt_copy_string_field(&dst->path, &src->path) != 0) { + return -1; + } + if (kzt_copy_string_field(&dst->soname, &src->soname) != 0) { + kzt_free_string_field(&dst->path); + return -1; + } + + return 0; +} + +static int kzt_snapshot_from_observation( + kzt_guest_object_snapshot_t *snapshot, + const kzt_guest_object_observation_t *observation, + unsigned long generation) +{ + memset(snapshot, 0, sizeof(*snapshot)); + + snapshot->link_map_addr = observation->link_map_addr; + snapshot->load_bias = observation->load_bias; + snapshot->dynamic_addr = observation->dynamic_addr; + snapshot->map_start = observation->map_start; + snapshot->map_end = observation->map_end; + snapshot->namespace_id = observation->namespace_id; + snapshot->dynamic_view_status = observation->dynamic_view_status; + snapshot->state = KZT_GUEST_OBJECT_DISCOVERED; + snapshot->generation = generation; + + if (kzt_copy_string_field(&snapshot->path, &observation->path) != 0) { + return -1; + } + if (kzt_copy_string_field(&snapshot->soname, &observation->soname) != 0) { + kzt_free_string_field(&snapshot->path); + return -1; + } + + return 0; +} + +static int kzt_scalar_conflicts(const kzt_guest_scalar_field_t *current, + const kzt_guest_scalar_field_t *incoming) +{ + return kzt_field_is_reliable(current->status) && + kzt_field_is_reliable(incoming->status) && + current->value != incoming->value; +} + +static int kzt_string_conflicts(const kzt_guest_string_field_t *current, + const kzt_guest_string_field_t *incoming) +{ + const char *left; + const char *right; + + if (!kzt_field_is_reliable(current->status) || + !kzt_field_is_reliable(incoming->status)) { + return 0; + } + + left = current->value ? current->value : ""; + right = incoming->value ? incoming->value : ""; + return strcmp(left, right) != 0; +} + +static int kzt_update_scalar_field(kzt_guest_scalar_field_t *current, + const kzt_guest_scalar_field_t *incoming) +{ + if (kzt_field_is_reliable(current->status) || + !kzt_field_is_reliable(incoming->status)) { + return 0; + } + + *current = *incoming; + return 1; +} + +static int kzt_update_string_field(kzt_guest_string_field_t *current, + const kzt_guest_string_field_t *incoming) +{ + char *copy; + + if (kzt_field_is_reliable(current->status) || + !kzt_field_is_reliable(incoming->status)) { + return 0; + } + + copy = kzt_registry_strdup(incoming->value); + if (!copy) { + return -1; + } + + kzt_free_string_field(current); + current->value = copy; + current->status = incoming->status; + return 1; +} + +static int kzt_update_status_field(kzt_guest_field_status_t *current, + kzt_guest_field_status_t incoming) +{ + if (kzt_field_is_reliable(*current) || + !kzt_field_is_reliable(incoming)) { + return 0; + } + + *current = incoming; + return 1; +} + +static int kzt_observation_conflicts( + const kzt_guest_object_snapshot_t *current, + const kzt_guest_object_observation_t *incoming) +{ + return kzt_scalar_conflicts(¤t->load_bias, + &incoming->load_bias) || + kzt_scalar_conflicts(¤t->dynamic_addr, + &incoming->dynamic_addr) || + kzt_scalar_conflicts(¤t->map_start, + &incoming->map_start) || + kzt_scalar_conflicts(¤t->map_end, + &incoming->map_end) || + kzt_scalar_conflicts(¤t->namespace_id, + &incoming->namespace_id) || + kzt_string_conflicts(¤t->path, &incoming->path) || + kzt_string_conflicts(¤t->soname, &incoming->soname); +} + +static int kzt_update_snapshot(kzt_guest_object_snapshot_t *current, + const kzt_guest_object_observation_t *incoming, + int *updated) +{ + int ret; + + *updated |= kzt_update_scalar_field(¤t->load_bias, + &incoming->load_bias); + *updated |= kzt_update_scalar_field(¤t->dynamic_addr, + &incoming->dynamic_addr); + *updated |= kzt_update_scalar_field(¤t->map_start, + &incoming->map_start); + *updated |= kzt_update_scalar_field(¤t->map_end, + &incoming->map_end); + *updated |= kzt_update_scalar_field(¤t->namespace_id, + &incoming->namespace_id); + + ret = kzt_update_string_field(¤t->path, &incoming->path); + if (ret < 0) { + return -1; + } + *updated |= ret; + + ret = kzt_update_string_field(¤t->soname, &incoming->soname); + if (ret < 0) { + return -1; + } + *updated |= ret; + + *updated |= kzt_update_status_field(¤t->dynamic_view_status, + incoming->dynamic_view_status); + return 0; +} + +static ssize_t kzt_find_object_index(kzt_guest_registry_t *registry, + uintptr_t link_map_addr) +{ + size_t i; + + for (i = 0; i < registry->count; ++i) { + if (registry->objects[i].link_map_addr == link_map_addr) { + return (ssize_t)i; + } + } + + return -1; +} + +static int kzt_registry_ensure_capacity(kzt_guest_registry_t *registry) +{ + kzt_guest_object_snapshot_t *objects; + size_t new_capacity; + + if (registry->count < registry->capacity) { + return 0; + } + + new_capacity = registry->capacity ? + registry->capacity * 2 : KZT_GUEST_REGISTRY_INITIAL_CAPACITY; + objects = kzt_registry_calloc(new_capacity, sizeof(*objects)); + if (!objects) { + ++registry->diagnostics.allocation_failures; + return -1; + } + + if (registry->objects) { + memcpy(objects, registry->objects, + registry->count * sizeof(*registry->objects)); + kzt_registry_free(registry->objects); + } + + registry->objects = objects; + registry->capacity = new_capacity; + return 0; +} + +static const char *kzt_registry_result_name(kzt_guest_registry_result_t result) +{ + switch (result) { + case KZT_GUEST_REGISTRY_ADDED: + return "added"; + case KZT_GUEST_REGISTRY_UNCHANGED: + return "unchanged"; + case KZT_GUEST_REGISTRY_UPDATED: + return "updated"; + case KZT_GUEST_REGISTRY_CONFLICT: + return "conflict"; + case KZT_GUEST_REGISTRY_DISABLED: + return "disabled"; + case KZT_GUEST_REGISTRY_ERROR: + return "error"; + case KZT_GUEST_REGISTRY_RESULT_COUNT: + break; + } + + return "unknown"; +} + +static const char *kzt_guest_field_status_name( + kzt_guest_field_status_t status) +{ + switch (status) { + case KZT_GUEST_FIELD_OK: + return "ok"; + case KZT_GUEST_FIELD_UNKNOWN: + return "unknown"; + case KZT_GUEST_FIELD_READ_ERROR: + return "read_error"; + case KZT_GUEST_FIELD_TRUNCATED: + return "truncated"; + case KZT_GUEST_FIELD_NOT_PARSED: + return "not_parsed"; + } + + return "invalid"; +} + +static void kzt_registry_note_counter(kzt_guest_registry_t *registry, + kzt_guest_registry_result_t result) +{ + switch (result) { + case KZT_GUEST_REGISTRY_ADDED: + ++registry->diagnostics.added; + break; + case KZT_GUEST_REGISTRY_UNCHANGED: + ++registry->diagnostics.unchanged; + break; + case KZT_GUEST_REGISTRY_UPDATED: + ++registry->diagnostics.updated; + break; + case KZT_GUEST_REGISTRY_CONFLICT: + ++registry->diagnostics.conflicts; + break; + case KZT_GUEST_REGISTRY_DISABLED: + ++registry->diagnostics.disabled; + break; + case KZT_GUEST_REGISTRY_ERROR: + ++registry->diagnostics.errors; + break; + case KZT_GUEST_REGISTRY_RESULT_COUNT: + break; + } +} + +static void kzt_registry_init_empty_diagnostic( + kzt_guest_registry_observation_diagnostic_t *diagnostic, + kzt_guest_registry_result_t result, + uintptr_t link_map_addr) +{ + if (!diagnostic) { + return; + } + + memset(diagnostic, 0, sizeof(*diagnostic)); + diagnostic->result = result; + diagnostic->link_map_addr = link_map_addr; +} + +static void kzt_registry_note_result( + kzt_guest_registry_t *registry, + kzt_guest_registry_result_t result, + uintptr_t link_map_addr, + unsigned long generation, + kzt_guest_registry_observation_diagnostic_t *diagnostic) +{ + kzt_guest_registry_event_summary_t *event = NULL; + int emitted = 0; + + kzt_registry_note_counter(registry, result); + + if (registry->diagnostic_config.enabled && + result < KZT_GUEST_REGISTRY_RESULT_COUNT) { + event = ®istry->diagnostic_events[result]; + event->result = result; + ++event->observed; + event->last_link_map_addr = link_map_addr; + event->last_generation = generation; + if (event->emitted < registry->diagnostic_config.throttle_limit) { + ++event->emitted; + emitted = 1; + } else { + ++event->suppressed; + } + } + + if (!diagnostic) { + return; + } + + memset(diagnostic, 0, sizeof(*diagnostic)); + diagnostic->enabled = registry->diagnostic_config.enabled; + diagnostic->emitted = emitted; + diagnostic->result = result; + diagnostic->link_map_addr = link_map_addr; + diagnostic->generation = generation; + diagnostic->object_count = registry->count; + diagnostic->counters = registry->diagnostics; + if (event) { + diagnostic->result_observations = event->observed; + diagnostic->result_suppressed = event->suppressed; + } +} + +kzt_guest_registry_t *kzt_guest_registry_init(void) +{ + kzt_guest_registry_t *registry; + + registry = kzt_registry_calloc(1, sizeof(*registry)); + if (!registry) { + return NULL; + } + + if (pthread_mutex_init(®istry->lock, NULL) != 0) { + kzt_registry_free(registry); + return NULL; + } + + registry->lock_ready = 1; + registry->next_generation = 1; + registry->capacity = KZT_GUEST_REGISTRY_INITIAL_CAPACITY; + registry->objects = kzt_registry_calloc(registry->capacity, + sizeof(*registry->objects)); + if (!registry->objects) { + registry->capacity = 0; + registry->disabled = 1; + ++registry->diagnostics.init_failures; + ++registry->diagnostics.allocation_failures; + } + + return registry; +} + +void kzt_guest_registry_destroy(kzt_guest_registry_t **registry_ptr) +{ + kzt_guest_registry_t *registry; + + if (!registry_ptr || !*registry_ptr) { + return; + } + + registry = *registry_ptr; + *registry_ptr = NULL; + + if (registry->lock_ready) { + pthread_mutex_lock(®istry->lock); + registry->disabled = 1; + pthread_mutex_unlock(®istry->lock); + } + + kzt_free_snapshot_array(registry->objects, registry->count); + registry->objects = NULL; + registry->count = 0; + registry->capacity = 0; + + if (registry->lock_ready) { + pthread_mutex_destroy(®istry->lock); + } + + kzt_registry_free(registry); +} + +kzt_guest_registry_result_t kzt_guest_registry_observe( + kzt_guest_registry_t *registry, + const kzt_guest_object_observation_t *observation) +{ + return kzt_guest_registry_observe_with_diagnostic(registry, observation, + NULL); +} + +kzt_guest_registry_result_t kzt_guest_registry_observe_with_diagnostic( + kzt_guest_registry_t *registry, + const kzt_guest_object_observation_t *observation, + kzt_guest_registry_observation_diagnostic_t *diagnostic) +{ + kzt_guest_registry_result_t result; + ssize_t index; + int updated = 0; + uintptr_t link_map_addr = observation ? observation->link_map_addr : 0; + unsigned long generation = 0; + + if (!registry) { + kzt_registry_init_empty_diagnostic(diagnostic, + KZT_GUEST_REGISTRY_DISABLED, + link_map_addr); + return KZT_GUEST_REGISTRY_DISABLED; + } + + pthread_mutex_lock(®istry->lock); + ++registry->diagnostics.observations; + + if (registry->disabled) { + result = KZT_GUEST_REGISTRY_DISABLED; + goto out; + } + + if (!observation || observation->link_map_addr == 0) { + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + + index = kzt_find_object_index(registry, observation->link_map_addr); + if (index < 0) { + if (kzt_registry_ensure_capacity(registry) != 0) { + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + generation = registry->next_generation; + if (kzt_snapshot_from_observation(®istry->objects[registry->count], + observation, generation) != 0) { + ++registry->diagnostics.allocation_failures; + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + ++registry->next_generation; + ++registry->count; + result = KZT_GUEST_REGISTRY_ADDED; + goto out; + } + + generation = registry->objects[index].generation; + if (kzt_observation_conflicts(®istry->objects[index], observation)) { + result = KZT_GUEST_REGISTRY_CONFLICT; + goto out; + } + + if (kzt_update_snapshot(®istry->objects[index], observation, + &updated) != 0) { + ++registry->diagnostics.allocation_failures; + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + + result = updated ? KZT_GUEST_REGISTRY_UPDATED : + KZT_GUEST_REGISTRY_UNCHANGED; + +out: + kzt_registry_note_result(registry, result, link_map_addr, generation, + diagnostic); + pthread_mutex_unlock(®istry->lock); + return result; +} + +int kzt_guest_registry_find_by_link_map( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_object_snapshot_t **snapshot) +{ + ssize_t index; + + if (snapshot) { + *snapshot = NULL; + } + if (!registry || !snapshot || link_map_addr == 0) { + return -1; + } + + pthread_mutex_lock(®istry->lock); + if (registry->disabled) { + pthread_mutex_unlock(®istry->lock); + return -1; + } + + index = kzt_find_object_index(registry, link_map_addr); + if (index < 0) { + pthread_mutex_unlock(®istry->lock); + return -1; + } + + *snapshot = kzt_registry_calloc(1, sizeof(**snapshot)); + if (!*snapshot) { + ++registry->diagnostics.allocation_failures; + pthread_mutex_unlock(®istry->lock); + return -1; + } + + if (kzt_copy_snapshot(*snapshot, ®istry->objects[index]) != 0) { + ++registry->diagnostics.allocation_failures; + kzt_registry_free(*snapshot); + *snapshot = NULL; + pthread_mutex_unlock(®istry->lock); + return -1; + } + + pthread_mutex_unlock(®istry->lock); + return 0; +} + +kzt_guest_registry_result_t kzt_guest_registry_commit_dynamic_view( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + const kzt_guest_dynamic_view_t *view) +{ + kzt_guest_object_snapshot_t *object; + kzt_guest_field_status_t status; + kzt_guest_registry_result_t result; + ssize_t index; + + if (!registry) { + return KZT_GUEST_REGISTRY_DISABLED; + } + + if (!view || link_map_addr == 0) { + return KZT_GUEST_REGISTRY_ERROR; + } + +#ifdef KZT_GUEST_REGISTRY_TEST + if (kzt_registry_test_should_fail_dynamic_commit()) { + return KZT_GUEST_REGISTRY_ERROR; + } +#endif + + pthread_mutex_lock(®istry->lock); + if (registry->disabled) { + result = KZT_GUEST_REGISTRY_DISABLED; + goto out; + } + + index = kzt_find_object_index(registry, link_map_addr); + if (index < 0) { + result = KZT_GUEST_REGISTRY_ERROR; + goto out; + } + + object = ®istry->objects[index]; + status = kzt_dynamic_view_field_status(view); + if (object->dynamic_view_status == status && + kzt_dynamic_view_equal(&object->dynamic_view, view)) { + result = KZT_GUEST_REGISTRY_UNCHANGED; + goto out; + } + + object->dynamic_view = *view; + object->dynamic_view_status = status; + if (status == KZT_GUEST_FIELD_OK) { + object->state = KZT_GUEST_OBJECT_PARSED; + } else if (object->state == KZT_GUEST_OBJECT_PARSED) { + object->state = KZT_GUEST_OBJECT_DISCOVERED; + } + result = KZT_GUEST_REGISTRY_UPDATED; + +out: + pthread_mutex_unlock(®istry->lock); + return result; +} + +int kzt_guest_registry_find_dynamic_view( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_dynamic_view_t *view, + kzt_guest_field_status_t *status, + unsigned long *generation) +{ + ssize_t index; + + if (view) { + memset(view, 0, sizeof(*view)); + } + if (status) { + *status = KZT_GUEST_FIELD_NOT_PARSED; + } + if (generation) { + *generation = 0; + } + if (!registry || !view || link_map_addr == 0) { + return -1; + } + + pthread_mutex_lock(®istry->lock); + if (registry->disabled) { + pthread_mutex_unlock(®istry->lock); + return -1; + } + + index = kzt_find_object_index(registry, link_map_addr); + if (index < 0) { + pthread_mutex_unlock(®istry->lock); + return -1; + } + + *view = registry->objects[index].dynamic_view; + if (status) { + *status = registry->objects[index].dynamic_view_status; + } + if (generation) { + *generation = registry->objects[index].generation; + } + + pthread_mutex_unlock(®istry->lock); + return 0; +} + +int kzt_guest_registry_dump_snapshot( + kzt_guest_registry_t *registry, + kzt_guest_registry_dump_t *dump) +{ + size_t i; + + if (dump) { + dump->objects = NULL; + dump->count = 0; + } + if (!registry || !dump) { + return -1; + } + + pthread_mutex_lock(®istry->lock); + if (registry->disabled) { + pthread_mutex_unlock(®istry->lock); + return -1; + } + + if (registry->count == 0) { + pthread_mutex_unlock(®istry->lock); + return 0; + } + + dump->objects = kzt_registry_calloc(registry->count, + sizeof(*dump->objects)); + if (!dump->objects) { + ++registry->diagnostics.allocation_failures; + pthread_mutex_unlock(®istry->lock); + return -1; + } + + for (i = 0; i < registry->count; ++i) { + if (kzt_copy_snapshot(&dump->objects[i], ®istry->objects[i]) != 0) { + ++registry->diagnostics.allocation_failures; + kzt_free_snapshot_array(dump->objects, i + 1); + dump->objects = NULL; + dump->count = 0; + pthread_mutex_unlock(®istry->lock); + return -1; + } + } + dump->count = registry->count; + + pthread_mutex_unlock(®istry->lock); + return 0; +} + +int kzt_guest_registry_get_diagnostics( + kzt_guest_registry_t *registry, + kzt_guest_registry_diagnostics_t *diagnostics) +{ + if (!registry || !diagnostics) { + return -1; + } + + pthread_mutex_lock(®istry->lock); + *diagnostics = registry->diagnostics; + pthread_mutex_unlock(®istry->lock); + return 0; +} + +int kzt_guest_registry_configure_diagnostics( + kzt_guest_registry_t *registry, + const kzt_guest_registry_diagnostic_config_t *config) +{ + if (!registry || !config) { + return -1; + } + + pthread_mutex_lock(®istry->lock); + registry->diagnostic_config.enabled = !!config->enabled; + registry->diagnostic_config.throttle_limit = + config->throttle_limit ? config->throttle_limit : 1; + pthread_mutex_unlock(®istry->lock); + return 0; +} + +int kzt_guest_registry_get_diagnostic_report( + kzt_guest_registry_t *registry, + kzt_guest_registry_diagnostic_report_t *report) +{ + if (!registry || !report) { + return -1; + } + + pthread_mutex_lock(®istry->lock); + memset(report, 0, sizeof(*report)); + report->config = registry->diagnostic_config; + report->counters = registry->diagnostics; + memcpy(report->events, registry->diagnostic_events, + sizeof(report->events)); + report->event_count = KZT_GUEST_REGISTRY_RESULT_COUNT; + pthread_mutex_unlock(®istry->lock); + return 0; +} + +int kzt_guest_registry_note_diagnostic( + kzt_guest_registry_t *registry, + kzt_guest_registry_result_t result, + uintptr_t link_map_addr, + kzt_guest_registry_observation_diagnostic_t *diagnostic) +{ + if (!registry || result >= KZT_GUEST_REGISTRY_RESULT_COUNT) { + kzt_registry_init_empty_diagnostic(diagnostic, result, link_map_addr); + return -1; + } + + pthread_mutex_lock(®istry->lock); + kzt_registry_note_result(registry, result, link_map_addr, 0, diagnostic); + pthread_mutex_unlock(®istry->lock); + return 0; +} + +static int kzt_guest_registry_dump_emit( + kzt_guest_registry_dump_sink_fn sink, + void *opaque, + const char *fmt, + ...) +{ + char line[1024]; + va_list ap; + int len; + + va_start(ap, fmt); + len = vsnprintf(line, sizeof(line), fmt, ap); + va_end(ap); + if (len < 0) { + return -1; + } + + line[sizeof(line) - 1] = '\0'; + return sink(line, opaque); +} + +static int kzt_guest_registry_dump_emit_scalar( + kzt_guest_registry_dump_sink_fn sink, + void *opaque, + const char *name, + kzt_guest_scalar_field_t field) +{ + return kzt_guest_registry_dump_emit( + sink, opaque, "%s=0x%lx(%s)", name, (unsigned long)field.value, + kzt_guest_field_status_name(field.status)); +} + +int kzt_guest_registry_dump_text( + kzt_guest_registry_t *registry, + kzt_guest_registry_dump_sink_fn sink, + void *opaque) +{ + kzt_guest_registry_diagnostic_report_t report; + kzt_guest_registry_dump_t dump = { 0 }; + size_t i; + int ret = -1; + + if (!registry || !sink) { + return -1; + } + + if (kzt_guest_registry_get_diagnostic_report(registry, &report) != 0) { + return -1; + } + if (kzt_guest_registry_dump_snapshot(registry, &dump) != 0) { + return -1; + } + + if (kzt_guest_registry_dump_emit( + sink, opaque, + "kzt_guest_registry diagnostics enabled=%d throttle_limit=%lu " + "observations=%lu added=%lu unchanged=%lu updated=%lu " + "conflicts=%lu disabled=%lu errors=%lu init_failures=%lu " + "allocation_failures=%lu objects=%lu", + report.config.enabled, report.config.throttle_limit, + report.counters.observations, report.counters.added, + report.counters.unchanged, report.counters.updated, + report.counters.conflicts, report.counters.disabled, + report.counters.errors, report.counters.init_failures, + report.counters.allocation_failures, + (unsigned long)dump.count) != 0) { + goto out; + } + + for (i = 0; i < report.event_count; ++i) { + const kzt_guest_registry_event_summary_t *event = &report.events[i]; + + if (event->observed == 0) { + continue; + } + if (kzt_guest_registry_dump_emit( + sink, opaque, + "kzt_guest_registry event result=%s observed=%lu " + "emitted=%lu suppressed=%lu last_link_map=0x%lx " + "last_generation=%lu", + kzt_registry_result_name(event->result), event->observed, + event->emitted, event->suppressed, + (unsigned long)event->last_link_map_addr, + event->last_generation) != 0) { + goto out; + } + } + + for (i = 0; i < dump.count; ++i) { + const kzt_guest_object_snapshot_t *object = &dump.objects[i]; + + if (kzt_guest_registry_dump_emit( + sink, opaque, + "kzt_guest_registry object link_map=0x%lx generation=%lu " + "state=%d ", + (unsigned long)object->link_map_addr, object->generation, + object->state) != 0 || + kzt_guest_registry_dump_emit_scalar(sink, opaque, "load_bias", + object->load_bias) != 0 || + kzt_guest_registry_dump_emit_scalar(sink, opaque, " dynamic_addr", + object->dynamic_addr) != 0 || + kzt_guest_registry_dump_emit_scalar(sink, opaque, " map_start", + object->map_start) != 0 || + kzt_guest_registry_dump_emit_scalar(sink, opaque, " map_end", + object->map_end) != 0 || + kzt_guest_registry_dump_emit( + sink, opaque, + " namespace_id=0x%lx(%s) dynamic_view=%s path_status=%s " + "path=\"%s\" soname_status=%s soname=\"%s\"", + (unsigned long)object->namespace_id.value, + kzt_guest_field_status_name(object->namespace_id.status), + kzt_guest_field_status_name(object->dynamic_view_status), + kzt_guest_field_status_name(object->path.status), + object->path.value ? object->path.value : "", + kzt_guest_field_status_name(object->soname.status), + object->soname.value ? object->soname.value : "") != 0) { + goto out; + } + } + + ret = 0; + +out: + kzt_guest_registry_dump_free(&dump); + return ret; +} + +void kzt_guest_object_snapshot_free(kzt_guest_object_snapshot_t *snapshot) +{ + if (!snapshot) { + return; + } + + kzt_free_snapshot_strings(snapshot); + kzt_registry_free(snapshot); +} + +void kzt_guest_registry_dump_free(kzt_guest_registry_dump_t *dump) +{ + if (!dump) { + return; + } + + kzt_free_snapshot_array(dump->objects, dump->count); + dump->objects = NULL; + dump->count = 0; +} diff --git a/target/i386/latx/context/kzt_observation_adapter.c b/target/i386/latx/context/kzt_observation_adapter.c new file mode 100644 index 0000000000..5a913f40cb --- /dev/null +++ b/target/i386/latx/context/kzt_observation_adapter.c @@ -0,0 +1,228 @@ +#include "kzt_observation_adapter.h" + +#include + +static kzt_observation_adapter_result_t kzt_adapter_result_from_registry( + kzt_guest_registry_result_t registry_result) +{ + switch (registry_result) { + case KZT_GUEST_REGISTRY_ADDED: + return KZT_OBSERVATION_ADAPTER_ADDED; + case KZT_GUEST_REGISTRY_UNCHANGED: + return KZT_OBSERVATION_ADAPTER_UNCHANGED; + case KZT_GUEST_REGISTRY_UPDATED: + return KZT_OBSERVATION_ADAPTER_UPDATED; + case KZT_GUEST_REGISTRY_CONFLICT: + return KZT_OBSERVATION_ADAPTER_CONFLICT; + case KZT_GUEST_REGISTRY_DISABLED: + case KZT_GUEST_REGISTRY_ERROR: + return KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED; + case KZT_GUEST_REGISTRY_RESULT_COUNT: + break; + } + + return KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED; +} + +static int kzt_adapter_registry_result_allows_dynamic_parse( + kzt_guest_registry_result_t registry_result) +{ + return registry_result == KZT_GUEST_REGISTRY_ADDED || + registry_result == KZT_GUEST_REGISTRY_UNCHANGED || + registry_result == KZT_GUEST_REGISTRY_UPDATED; +} + +static void kzt_adapter_note_dynamic_failure( + const kzt_observation_adapter_request_t *request, + kzt_guest_registry_result_t result, + kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_guest_registry_observation_diagnostic_t *registry_diagnostic = NULL; + + if (dynamic_diagnostic) { + registry_diagnostic = &dynamic_diagnostic->registry; + } + + (void)kzt_guest_registry_note_diagnostic( + request ? request->registry : NULL, result, + request ? request->link_map_addr : 0, registry_diagnostic); +} + +static void kzt_observe_guest_dynamic_view( + const kzt_observation_adapter_request_t *request, + const kzt_guest_object_observation_t *observation, + kzt_guest_registry_result_t registry_result, + kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_guest_dynamic_parse_result_t parse_result = { 0 }; + kzt_guest_registry_result_t commit_result; + int parse_return; + + if (dynamic_diagnostic) { + memset(dynamic_diagnostic, 0, sizeof(*dynamic_diagnostic)); + dynamic_diagnostic->commit_result = KZT_GUEST_REGISTRY_RESULT_COUNT; + } + + if (!request || !observation || + !kzt_adapter_registry_result_allows_dynamic_parse(registry_result)) { + return; + } + + if (observation->dynamic_addr.status != KZT_GUEST_FIELD_OK || + observation->dynamic_addr.value == 0 || + observation->load_bias.status != KZT_GUEST_FIELD_OK) { + return; + } + + if (dynamic_diagnostic) { + dynamic_diagnostic->attempted = 1; + dynamic_diagnostic->dynamic_addr = observation->dynamic_addr.value; + } + + parse_return = kzt_guest_dynamic_parse(observation->dynamic_addr.value, + observation->load_bias.value, + request->reader_ops, + &parse_result); + if (dynamic_diagnostic) { + dynamic_diagnostic->parse_return = parse_return; + dynamic_diagnostic->status = parse_result.status; + dynamic_diagnostic->error = parse_result.error; + dynamic_diagnostic->entry_count = parse_result.entry_count; + dynamic_diagnostic->read_error_addr = parse_result.read_error_addr; + } + + if (parse_return != 0) { + kzt_adapter_note_dynamic_failure( + request, KZT_GUEST_REGISTRY_ERROR, dynamic_diagnostic); + kzt_guest_dynamic_parse_result_clear(&parse_result); + return; + } + + if (parse_result.status != KZT_GUEST_DYNAMIC_COMPLETE) { + kzt_adapter_note_dynamic_failure( + request, KZT_GUEST_REGISTRY_ERROR, dynamic_diagnostic); + } + + commit_result = kzt_guest_registry_commit_dynamic_view( + request->registry, observation->link_map_addr, &parse_result.view); + if (dynamic_diagnostic) { + dynamic_diagnostic->commit_attempted = 1; + dynamic_diagnostic->commit_result = commit_result; + } + if (commit_result == KZT_GUEST_REGISTRY_DISABLED || + commit_result == KZT_GUEST_REGISTRY_ERROR) { + kzt_adapter_note_dynamic_failure(request, commit_result, + dynamic_diagnostic); + } + + kzt_guest_dynamic_parse_result_clear(&parse_result); +} + +static kzt_observation_adapter_result_t kzt_observe_guest_object( + const kzt_observation_adapter_request_t *request, + kzt_guest_registry_observation_diagnostic_t *registry_diagnostic, + kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_guest_object_observation_t observation; + kzt_guest_registry_result_t registry_result; + + if (!request || !request->enabled) { + if (request) { + kzt_guest_registry_note_diagnostic( + request->registry, KZT_GUEST_REGISTRY_DISABLED, + request->link_map_addr, registry_diagnostic); + } + return KZT_OBSERVATION_ADAPTER_DISABLED; + } + + if (kzt_guest_link_map_read_observation(request->link_map_addr, + request->reader_ops, + &observation) != 0) { + kzt_guest_registry_note_diagnostic( + request->registry, KZT_GUEST_REGISTRY_ERROR, + request->link_map_addr, registry_diagnostic); + return KZT_OBSERVATION_ADAPTER_READER_FAILED; + } + + if (observation.link_map_addr == 0 || + observation.load_bias.status != KZT_GUEST_FIELD_OK) { + kzt_guest_link_map_observation_clear(&observation); + kzt_guest_registry_note_diagnostic( + request->registry, KZT_GUEST_REGISTRY_ERROR, + request->link_map_addr, registry_diagnostic); + return KZT_OBSERVATION_ADAPTER_READER_FAILED; + } + + registry_result = kzt_guest_registry_observe_with_diagnostic( + request->registry, &observation, registry_diagnostic); + kzt_observe_guest_dynamic_view(request, &observation, registry_result, + dynamic_diagnostic); + kzt_guest_link_map_observation_clear(&observation); + + return kzt_adapter_result_from_registry(registry_result); +} + +static void kzt_observation_adapter_emit_diagnostic( + const kzt_observation_adapter_request_t *request, + kzt_observation_adapter_result_t result, + const kzt_guest_registry_observation_diagnostic_t *registry_diagnostic, + const kzt_observation_adapter_dynamic_diagnostic_t *dynamic_diagnostic) +{ + kzt_observation_adapter_diagnostic_t diagnostic = { 0 }; + + if (!request || !request->diagnostics_enabled || !request->diagnostic) { + return; + } + + diagnostic.enabled = 1; + diagnostic.result = result; + diagnostic.link_map_addr = request->link_map_addr; + diagnostic.emitted = 0; + if (registry_diagnostic) { + diagnostic.registry = *registry_diagnostic; + diagnostic.emitted = registry_diagnostic->enabled && + registry_diagnostic->emitted; + } + if (dynamic_diagnostic) { + diagnostic.dynamic = *dynamic_diagnostic; + if (dynamic_diagnostic->registry.enabled && + dynamic_diagnostic->registry.emitted) { + diagnostic.emitted = 1; + } + } + + if (!diagnostic.emitted) { + return; + } + + request->diagnostic(&diagnostic, request->diagnostic_opaque); +} + +int kzt_observe_guest_object_from_callback( + const kzt_observation_adapter_request_t *request, + kzt_observation_adapter_result_t *result) +{ + kzt_observation_adapter_result_t observation_result; + kzt_guest_registry_observation_diagnostic_t registry_diagnostic = { 0 }; + kzt_observation_adapter_dynamic_diagnostic_t dynamic_diagnostic = { + .commit_result = KZT_GUEST_REGISTRY_RESULT_COUNT, + }; + int legacy_ret = 0; + + observation_result = kzt_observe_guest_object(request, + ®istry_diagnostic, + &dynamic_diagnostic); + if (result) { + *result = observation_result; + } + + if (request && request->legacy_flow) { + legacy_ret = request->legacy_flow(request->link_map_addr, + request->legacy_opaque); + } + + kzt_observation_adapter_emit_diagnostic(request, observation_result, + ®istry_diagnostic, + &dynamic_diagnostic); + return legacy_ret; +} diff --git a/target/i386/latx/context/meson.build b/target/i386/latx/context/meson.build index f07cf9b44a..621b9131c2 100644 --- a/target/i386/latx/context/meson.build +++ b/target/i386/latx/context/meson.build @@ -68,6 +68,10 @@ my_file = files( 'bridge.c', 'wrapperdebug.c', 'wrapper.c', + 'kzt_guest_registry.c', + 'kzt_guest_link_map_reader.c', + 'kzt_guest_dynamic.c', + 'kzt_observation_adapter.c', 'myalign.c', 'obstack.c', 'globalsymbols.c', diff --git a/target/i386/latx/context/myalign.c b/target/i386/latx/context/myalign.c index d5ee1482f4..d77582763e 100644 --- a/target/i386/latx/context/myalign.c +++ b/target/i386/latx/context/myalign.c @@ -1,8 +1,12 @@ #include "config-host.h" #include "lsenv.h" #include "myalign.h" +#include "debug.h" #include "elfloader.h" #include "elfloader_private.h" +#ifdef CONFIG_LATX_KZT +#include "kzt_observation_adapter.h" +#endif #include #include #include @@ -33,6 +37,41 @@ typedef union { } mmx87_regs_t; static int regs_abi[] = {R_EDI, R_ESI, R_EDX, R_ECX, R_R8, R_R9}; + +#ifdef CONFIG_LATX_KZT +static int kzt_registry_debug_dump_line(const char *line, void *opaque) +{ + (void)opaque; + + printf_kzt_registry_diagnostics("%s\n", line); + return 0; +} + +static void kzt_callback_diagnostic_log( + const kzt_observation_adapter_diagnostic_t *diagnostic, + void *opaque) +{ + if (!diagnostic) { + return; + } + + printf_kzt_registry_diagnostics( + "KZT registry callback result=%d link_map=0x%lx " + "registry_result=%d generation=%lu objects=%lu " + "observed=%lu suppressed=%lu\n", + diagnostic->result, (unsigned long)diagnostic->link_map_addr, + diagnostic->registry.result, diagnostic->registry.generation, + diagnostic->registry.object_count, + diagnostic->registry.result_observations, + diagnostic->registry.result_suppressed); + + if (opaque) { + (void)kzt_guest_registry_dump_text(opaque, kzt_registry_debug_dump_line, + NULL); + } +} +#endif + uintptr_t getVArgs(int pos, uintptr_t* b, int N) { CPUX86State *cpu = (CPUX86State *)lsenv->cpu_state; @@ -2214,19 +2253,47 @@ static char* kzt_find_realsofilepath(char * filepath, char *filetmp) return filepath; } extern const char* libcName; -static void kzt_tb_callback(CPUX86State *env) +#ifdef CONFIG_LATX_KZT +static int kzt_callback_read_memory(uintptr_t guest_addr, + void *dst, + size_t size, + void *opaque) { - struct link_map_x64 * my_lm = (struct link_map_x64 *)env->regs[R_EAX + ld_info->reg]; + void *host_ptr; + + (void)opaque; + if (!dst && size) { + return -1; + } + if (!size) { + return 0; + } + + host_ptr = lock_user(VERIFY_READ, (abi_ulong)guest_addr, size, true); + if (!host_ptr) { + return -1; + } + + memcpy(dst, host_ptr, size); + unlock_user(host_ptr, (abi_ulong)guest_addr, 0); + return 0; +} +#endif + +static int kzt_tb_callback_legacy(uintptr_t link_map_addr, void *opaque) +{ + (void)opaque; + struct link_map_x64 * my_lm = (struct link_map_x64 *)link_map_addr; elfheader_t *h = NULL; if ((!my_lm ||!my_lm->l_name ||!strlen(my_lm->l_name) || ! my_lm->l_addr)&& my_lm->l_addr != info1.load_addr) { printf_log(LOG_DEBUG, "error %d debug %s link_map = %p{0x%lx, %s}\n", getpid(), __func__, my_lm, my_lm->l_addr, my_lm->l_name); - return; + return 0; } printf_log(LOG_DEBUG, "%d debug %s link_map = %p{0x%lx, %s}\n", getpid(), __func__, my_lm, my_lm->l_addr, my_lm->l_name); char * rfilename = my_lm->l_name; if (strstr(basename(rfilename), "ld-linux-x86-64.so.2")) { AddDebugInfo(LIB_EMULATED, my_lm->l_name, my_lm->l_map_start, my_lm->l_map_end); - return; + return 0; } char filetmp[PATH_MAX] = {0}; if (rfilename[0] == '/') { @@ -2246,7 +2313,7 @@ static void kzt_tb_callback(CPUX86State *env) FILE *f = fopen(rfilename, "rb"); if(!f) { printf_log(LOG_INFO, "%s Error: Cannot open \"%s\"\n", __func__, rfilename); - return; + return 0; } h = LoadAndCheckElfHeader(f, rfilename, 0); ElfHeadReFix(h, my_lm->l_addr); @@ -2270,6 +2337,42 @@ static void kzt_tb_callback(CPUX86State *env) } else { AddDebugInfo(LIB_EMULATED, my_lm->l_name, my_lm->l_map_start, my_lm->l_map_end); } + return 0; +} + +static void kzt_tb_callback(CPUX86State *env) +{ + uintptr_t link_map_addr = env->regs[R_EAX + ld_info->reg]; +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_t *registry = KztGuestRegistryForContext(my_context); + kzt_guest_registry_diagnostic_config_t diagnostic_config = { + .enabled = option_kzt || wine_option_kzt, + .throttle_limit = 1, + }; + const kzt_guest_link_map_reader_ops_t reader_ops = { + .read_memory = kzt_callback_read_memory, + .opaque = NULL, + }; + kzt_observation_adapter_request_t request = { + .enabled = option_kzt || wine_option_kzt, + .diagnostics_enabled = kzt_registry_diagnostics_enabled(), + .link_map_addr = link_map_addr, + .registry = registry, + .reader_ops = &reader_ops, + .legacy_flow = kzt_tb_callback_legacy, + .legacy_opaque = NULL, + .diagnostic = kzt_callback_diagnostic_log, + .diagnostic_opaque = registry, + }; + + if (registry) { + (void)kzt_guest_registry_configure_diagnostics(registry, + &diagnostic_config); + } + (void)kzt_observe_guest_object_from_callback(&request, NULL); +#else + (void)kzt_tb_callback_legacy(link_map_addr, env); +#endif } static TranslationBlock* test_tb; static void test_x86free(CPUX86State *env) diff --git a/target/i386/latx/include/box64context.h b/target/i386/latx/include/box64context.h index 489d1b79ad..d317919e6d 100755 --- a/target/i386/latx/include/box64context.h +++ b/target/i386/latx/include/box64context.h @@ -10,6 +10,7 @@ typedef struct elfheader_s elfheader_t; typedef struct cleanup_s cleanup_t; typedef struct lib_s lib_t; typedef struct bridge_s bridge_t; +typedef struct kzt_guest_registry kzt_guest_registry_t; typedef struct kh_symbolmap_s kh_symbolmap_t; typedef struct kh_symbol1map_s kh_symbol1map_t; typedef struct library_s library_t; @@ -346,12 +347,18 @@ typedef struct box64context_s { int latx_kzt_debugcap; int latx_kzt_debugsize; // number of latx_kzt_debug #endif +#ifdef CONFIG_LATX_KZT + kzt_guest_registry_t *kzt_guest_registry; +#endif } box64context_t; extern box64context_t *my_context; // global context box64context_t *NewBox64Context(int argc); void FreeBox64Context(box64context_t** context); +#ifdef CONFIG_LATX_KZT +kzt_guest_registry_t *KztGuestRegistryForContext(box64context_t *context); +#endif // return the index of the added header int AddElfHeader(box64context_t* ctx, elfheader_t* head); diff --git a/target/i386/latx/include/debug.h b/target/i386/latx/include/debug.h index 2e302fa22c..cabe1ed3e2 100755 --- a/target/i386/latx/include/debug.h +++ b/target/i386/latx/include/debug.h @@ -13,6 +13,7 @@ extern int box64_pagesize; extern uintptr_t box64_load_addr; extern int dlsym_error; // log dlsym error extern int kzt_call_log; +extern int kzt_registry_diagnostics; extern int allow_missing_libs; extern int box64_nogtk; extern int box64_prefer_wrapped; @@ -50,6 +51,19 @@ extern char* libGL; #define printf_dlsym(L, ...) ((void)0) #define printf_kzt_call(L, ...) ((void)0) #endif +#if defined(CONFIG_LATX_KZT) +#define kzt_registry_diagnostics_enabled() \ + (kzt_registry_diagnostics || (LOG_DEBUG <= relocation_log)) +#define printf_kzt_registry_diagnostics(...) \ + do { \ + if (kzt_registry_diagnostics_enabled()) { \ + fprintf(stderr, __VA_ARGS__); \ + } \ + } while (0) +#else +#define kzt_registry_diagnostics_enabled() 0 +#define printf_kzt_registry_diagnostics(...) ((void)0) +#endif #define EXPORT __attribute__((visibility("default"))) #define EXPORTDYN diff --git a/target/i386/latx/include/kzt_guest_dynamic.h b/target/i386/latx/include/kzt_guest_dynamic.h new file mode 100644 index 0000000000..69d2579414 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dynamic.h @@ -0,0 +1,47 @@ +#ifndef KZT_GUEST_DYNAMIC_H +#define KZT_GUEST_DYNAMIC_H + +#include +#include + +#include "elf.h" +#include "kzt_guest_dynamic_view.h" +#include "kzt_guest_link_map_reader.h" + +#ifndef DT_GNU_HASH +#define DT_GNU_HASH 0x6ffffef5 +#endif + + +typedef enum kzt_guest_dynamic_error { + KZT_GUEST_DYNAMIC_ERROR_NONE = 0, + KZT_GUEST_DYNAMIC_ERROR_INVALID_ARGUMENT, + KZT_GUEST_DYNAMIC_ERROR_ALLOCATION_FAILURE, + KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE, + KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED, + KZT_GUEST_DYNAMIC_ERROR_TOO_MANY_NEEDED, +} kzt_guest_dynamic_error_t; + +typedef struct kzt_guest_dynamic_parse_result { + kzt_guest_dynamic_status_t status; + kzt_guest_dynamic_error_t error; + size_t entry_count; + uintptr_t read_error_addr; + size_t scan_limit; + size_t unknown_tag_count; + int64_t first_unknown_tag; + size_t first_unknown_tag_index; + kzt_guest_dynamic_view_t view; +} kzt_guest_dynamic_parse_result_t; + +int kzt_guest_dynamic_parse( + uintptr_t dynamic_addr, + uintptr_t load_bias, + const kzt_guest_link_map_reader_ops_t *reader_ops, + kzt_guest_dynamic_parse_result_t *result); + +void kzt_guest_dynamic_view_destroy(kzt_guest_dynamic_view_t *view); +void kzt_guest_dynamic_parse_result_clear( + kzt_guest_dynamic_parse_result_t *result); + +#endif diff --git a/target/i386/latx/include/kzt_guest_dynamic_diagnostics.h b/target/i386/latx/include/kzt_guest_dynamic_diagnostics.h new file mode 100644 index 0000000000..1225ff90a5 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dynamic_diagnostics.h @@ -0,0 +1,130 @@ +#ifndef KZT_GUEST_DYNAMIC_DIAGNOSTICS_H +#define KZT_GUEST_DYNAMIC_DIAGNOSTICS_H + +#include +#include + +#include "kzt_guest_dynamic.h" + +#define KZT_GUEST_DYNAMIC_DIAGNOSTIC_FIELD_LIMIT 32 + +typedef enum kzt_guest_dynamic_diagnostic_match { + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED = 0, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_OLD, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH, +} kzt_guest_dynamic_diagnostic_match_t; + +typedef enum kzt_guest_dynamic_diagnostic_difference_kind { + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_NONE = 0, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_STATUS, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_ENTRY_COUNT, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_UNKNOWN_TAGS, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_FIELD, +} kzt_guest_dynamic_diagnostic_difference_kind_t; + +typedef struct kzt_guest_dynamic_diagnostic_field { + const char *name; + kzt_guest_dynamic_diagnostic_match_t match; + int old_present; + uint64_t old_value; + kzt_guest_dynamic_address_semantics_t old_address_semantics; + size_t old_count; + int new_present; + uint64_t new_value; + kzt_guest_dynamic_address_semantics_t new_address_semantics; + size_t new_count; +} kzt_guest_dynamic_diagnostic_field_t; + +typedef struct kzt_guest_dynamic_diagnostic_report { + kzt_guest_dynamic_status_t old_status; + kzt_guest_dynamic_status_t new_status; + kzt_guest_dynamic_error_t old_error; + kzt_guest_dynamic_error_t new_error; + uintptr_t old_read_error_addr; + uintptr_t new_read_error_addr; + size_t old_entry_count; + size_t new_entry_count; + size_t old_unknown_tag_count; + size_t new_unknown_tag_count; + int64_t old_first_unknown_tag; + int64_t new_first_unknown_tag; + size_t old_first_unknown_tag_index; + size_t new_first_unknown_tag_index; + int old_truncated; + int new_truncated; + int old_read_error; + int new_read_error; + + kzt_guest_dynamic_diagnostic_match_t status_match; + kzt_guest_dynamic_diagnostic_match_t entry_count_match; + kzt_guest_dynamic_diagnostic_match_t unknown_tags_match; + + size_t field_count; + size_t matched_count; + size_t missing_old_count; + size_t missing_new_count; + size_t mismatch_count; + size_t difference_count; + size_t blocking_count; + + kzt_guest_dynamic_diagnostic_field_t + fields[KZT_GUEST_DYNAMIC_DIAGNOSTIC_FIELD_LIMIT]; +} kzt_guest_dynamic_diagnostic_report_t; + +typedef struct kzt_guest_dynamic_diagnostic_summary { + uintptr_t link_map_addr; + unsigned long generation; + int matched; + int blocking; + size_t difference_count; + size_t blocking_count; + + kzt_guest_dynamic_status_t old_status; + kzt_guest_dynamic_status_t new_status; + size_t old_entry_count; + size_t new_entry_count; + size_t old_unknown_tag_count; + size_t new_unknown_tag_count; + int64_t old_first_unknown_tag; + int64_t new_first_unknown_tag; + size_t old_first_unknown_tag_index; + size_t new_first_unknown_tag_index; + + kzt_guest_dynamic_diagnostic_difference_kind_t first_difference_kind; + const char *first_difference_name; + kzt_guest_dynamic_diagnostic_match_t first_difference_match; + int first_old_present; + int first_new_present; + uint64_t first_old_value; + uint64_t first_new_value; + size_t first_old_count; + size_t first_new_count; + int64_t first_old_tag; + int64_t first_new_tag; + size_t first_old_tag_index; + size_t first_new_tag_index; +} kzt_guest_dynamic_diagnostic_summary_t; + +int kzt_guest_dynamic_diagnostics_summarize( + const kzt_guest_dynamic_diagnostic_report_t *report, + uintptr_t link_map_addr, + unsigned long generation, + kzt_guest_dynamic_diagnostic_summary_t *summary); + +int kzt_guest_dynamic_diagnostics_compare( + const kzt_guest_dynamic_parse_result_t *old_result, + const kzt_guest_dynamic_parse_result_t *new_result, + kzt_guest_dynamic_diagnostic_report_t *report); + +const kzt_guest_dynamic_diagnostic_field_t * +kzt_guest_dynamic_diagnostic_find_field( + const kzt_guest_dynamic_diagnostic_report_t *report, + const char *name); + +int kzt_guest_dynamic_diagnostics_format_summary( + const kzt_guest_dynamic_diagnostic_summary_t *summary, + char *buffer, + size_t buffer_size); + +#endif diff --git a/target/i386/latx/include/kzt_guest_dynamic_view.h b/target/i386/latx/include/kzt_guest_dynamic_view.h new file mode 100644 index 0000000000..0bd61f5303 --- /dev/null +++ b/target/i386/latx/include/kzt_guest_dynamic_view.h @@ -0,0 +1,68 @@ +#ifndef KZT_GUEST_DYNAMIC_VIEW_H +#define KZT_GUEST_DYNAMIC_VIEW_H + +#include +#include + +#define KZT_GUEST_DYNAMIC_SCAN_LIMIT 512 +#define KZT_GUEST_DYNAMIC_NEEDED_LIMIT 32 + +typedef enum kzt_guest_dynamic_status { + KZT_GUEST_DYNAMIC_COMPLETE = 0, + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL, + KZT_GUEST_DYNAMIC_READ_ERROR, + KZT_GUEST_DYNAMIC_ERROR, +} kzt_guest_dynamic_status_t; + +typedef enum kzt_guest_dynamic_address_semantics { + KZT_GUEST_DYNAMIC_ADDRESS_UNKNOWN = 0, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS, + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET, + KZT_GUEST_DYNAMIC_SCALAR, +} kzt_guest_dynamic_address_semantics_t; + +typedef struct kzt_guest_dynamic_field { + int present; + uint64_t value; + kzt_guest_dynamic_address_semantics_t address_semantics; +} kzt_guest_dynamic_field_t; + +typedef struct kzt_guest_dynamic_view { + uintptr_t dynamic_addr; + uintptr_t load_bias; + kzt_guest_dynamic_status_t status; + size_t entry_count; + int has_null; + size_t scan_limit; + size_t unknown_tag_count; + int64_t first_unknown_tag; + size_t first_unknown_tag_index; + + kzt_guest_dynamic_field_t symtab; + kzt_guest_dynamic_field_t strtab; + kzt_guest_dynamic_field_t syment; + kzt_guest_dynamic_field_t strsz; + kzt_guest_dynamic_field_t hash; + kzt_guest_dynamic_field_t gnu_hash; + kzt_guest_dynamic_field_t versym; + kzt_guest_dynamic_field_t verneed; + kzt_guest_dynamic_field_t verneednum; + kzt_guest_dynamic_field_t verdef; + kzt_guest_dynamic_field_t verdefnum; + kzt_guest_dynamic_field_t rela; + kzt_guest_dynamic_field_t relasz; + kzt_guest_dynamic_field_t relaent; + kzt_guest_dynamic_field_t rel; + kzt_guest_dynamic_field_t relsz; + kzt_guest_dynamic_field_t relent; + kzt_guest_dynamic_field_t jmprel; + kzt_guest_dynamic_field_t pltrelsz; + kzt_guest_dynamic_field_t pltrel; + kzt_guest_dynamic_field_t pltgot; + + uint64_t needed_offsets[KZT_GUEST_DYNAMIC_NEEDED_LIMIT]; + size_t needed_count; + kzt_guest_dynamic_address_semantics_t needed_address_semantics; +} kzt_guest_dynamic_view_t; + +#endif diff --git a/target/i386/latx/include/kzt_guest_link_map_reader.h b/target/i386/latx/include/kzt_guest_link_map_reader.h new file mode 100644 index 0000000000..b87aedb5cf --- /dev/null +++ b/target/i386/latx/include/kzt_guest_link_map_reader.h @@ -0,0 +1,41 @@ +#ifndef KZT_GUEST_LINK_MAP_READER_H +#define KZT_GUEST_LINK_MAP_READER_H + +#include +#include + +#include "kzt_guest_registry.h" + +#define KZT_GUEST_LINK_MAP_NAME_LIMIT 4096 + +typedef int (*kzt_guest_memory_read_fn)(uintptr_t guest_addr, + void *dst, + size_t size, + void *opaque); + +typedef struct kzt_guest_link_map_reader_ops { + kzt_guest_memory_read_fn read_memory; + void *opaque; +} kzt_guest_link_map_reader_ops_t; + +int kzt_guest_link_map_read_name_snapshot( + uintptr_t guest_name_addr, + const kzt_guest_link_map_reader_ops_t *ops, + size_t max_len, + kzt_guest_string_field_t *name); + +int kzt_guest_link_map_read_observation( + uintptr_t link_map_addr, + const kzt_guest_link_map_reader_ops_t *ops, + kzt_guest_object_observation_t *observation); + +void kzt_guest_link_map_observation_clear( + kzt_guest_object_observation_t *observation); + +void kzt_guest_link_map_string_clear(kzt_guest_string_field_t *field); + +#ifdef KZT_GUEST_LINK_MAP_READER_TEST +void kzt_guest_link_map_reader_test_set_alloc_failure_after(long allocations); +#endif + +#endif diff --git a/target/i386/latx/include/kzt_guest_registry.h b/target/i386/latx/include/kzt_guest_registry.h new file mode 100644 index 0000000000..3b83660e4c --- /dev/null +++ b/target/i386/latx/include/kzt_guest_registry.h @@ -0,0 +1,192 @@ +#ifndef KZT_GUEST_REGISTRY_H +#define KZT_GUEST_REGISTRY_H + +#include +#include + +#include "kzt_guest_dynamic_view.h" + +typedef struct kzt_guest_registry kzt_guest_registry_t; + +typedef enum kzt_guest_object_state { + KZT_GUEST_OBJECT_DISCOVERED = 0, + KZT_GUEST_OBJECT_PARSED, + KZT_GUEST_OBJECT_WRAPPER_READY, + KZT_GUEST_OBJECT_PATCHED, + KZT_GUEST_OBJECT_UNLOADING, + KZT_GUEST_OBJECT_DEAD, +} kzt_guest_object_state_t; + +typedef enum kzt_guest_field_status { + KZT_GUEST_FIELD_OK = 0, + KZT_GUEST_FIELD_UNKNOWN, + KZT_GUEST_FIELD_READ_ERROR, + KZT_GUEST_FIELD_TRUNCATED, + KZT_GUEST_FIELD_NOT_PARSED, +} kzt_guest_field_status_t; + +typedef enum kzt_guest_registry_result { + KZT_GUEST_REGISTRY_ADDED = 0, + KZT_GUEST_REGISTRY_UNCHANGED, + KZT_GUEST_REGISTRY_UPDATED, + KZT_GUEST_REGISTRY_CONFLICT, + KZT_GUEST_REGISTRY_DISABLED, + KZT_GUEST_REGISTRY_ERROR, + KZT_GUEST_REGISTRY_RESULT_COUNT, +} kzt_guest_registry_result_t; + +typedef struct kzt_guest_scalar_field { + uintptr_t value; + kzt_guest_field_status_t status; +} kzt_guest_scalar_field_t; + +typedef struct kzt_guest_string_field { + const char *value; + kzt_guest_field_status_t status; +} kzt_guest_string_field_t; + +typedef struct kzt_guest_object_observation { + uintptr_t link_map_addr; + kzt_guest_scalar_field_t load_bias; + kzt_guest_scalar_field_t dynamic_addr; + kzt_guest_scalar_field_t map_start; + kzt_guest_scalar_field_t map_end; + kzt_guest_scalar_field_t namespace_id; + kzt_guest_string_field_t path; + kzt_guest_string_field_t soname; + kzt_guest_field_status_t dynamic_view_status; +} kzt_guest_object_observation_t; + +typedef struct kzt_guest_object_snapshot { + uintptr_t link_map_addr; + kzt_guest_scalar_field_t load_bias; + kzt_guest_scalar_field_t dynamic_addr; + kzt_guest_scalar_field_t map_start; + kzt_guest_scalar_field_t map_end; + kzt_guest_scalar_field_t namespace_id; + kzt_guest_string_field_t path; + kzt_guest_string_field_t soname; + kzt_guest_field_status_t dynamic_view_status; + kzt_guest_dynamic_view_t dynamic_view; + kzt_guest_object_state_t state; + unsigned long generation; +} kzt_guest_object_snapshot_t; + +typedef struct kzt_guest_registry_dump { + kzt_guest_object_snapshot_t *objects; + size_t count; +} kzt_guest_registry_dump_t; + +typedef struct kzt_guest_registry_diagnostics { + unsigned long observations; + unsigned long added; + unsigned long unchanged; + unsigned long updated; + unsigned long conflicts; + unsigned long disabled; + unsigned long errors; + unsigned long init_failures; + unsigned long allocation_failures; +} kzt_guest_registry_diagnostics_t; + +typedef struct kzt_guest_registry_diagnostic_config { + int enabled; + unsigned long throttle_limit; +} kzt_guest_registry_diagnostic_config_t; + +typedef struct kzt_guest_registry_observation_diagnostic { + int enabled; + int emitted; + kzt_guest_registry_result_t result; + uintptr_t link_map_addr; + unsigned long generation; + unsigned long object_count; + unsigned long result_observations; + unsigned long result_suppressed; + kzt_guest_registry_diagnostics_t counters; +} kzt_guest_registry_observation_diagnostic_t; + +typedef struct kzt_guest_registry_event_summary { + kzt_guest_registry_result_t result; + unsigned long observed; + unsigned long emitted; + unsigned long suppressed; + uintptr_t last_link_map_addr; + unsigned long last_generation; +} kzt_guest_registry_event_summary_t; + +typedef struct kzt_guest_registry_diagnostic_report { + kzt_guest_registry_diagnostic_config_t config; + kzt_guest_registry_diagnostics_t counters; + kzt_guest_registry_event_summary_t events[KZT_GUEST_REGISTRY_RESULT_COUNT]; + size_t event_count; +} kzt_guest_registry_diagnostic_report_t; + +typedef int (*kzt_guest_registry_dump_sink_fn)(const char *line, + void *opaque); + +kzt_guest_registry_t *kzt_guest_registry_init(void); +void kzt_guest_registry_destroy(kzt_guest_registry_t **registry); + +kzt_guest_registry_result_t kzt_guest_registry_observe( + kzt_guest_registry_t *registry, + const kzt_guest_object_observation_t *observation); + +kzt_guest_registry_result_t kzt_guest_registry_observe_with_diagnostic( + kzt_guest_registry_t *registry, + const kzt_guest_object_observation_t *observation, + kzt_guest_registry_observation_diagnostic_t *diagnostic); + +int kzt_guest_registry_find_by_link_map( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_object_snapshot_t **snapshot); + +kzt_guest_registry_result_t kzt_guest_registry_commit_dynamic_view( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + const kzt_guest_dynamic_view_t *view); + +int kzt_guest_registry_find_dynamic_view( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr, + kzt_guest_dynamic_view_t *view, + kzt_guest_field_status_t *status, + unsigned long *generation); + +int kzt_guest_registry_dump_snapshot( + kzt_guest_registry_t *registry, + kzt_guest_registry_dump_t *dump); + +int kzt_guest_registry_get_diagnostics( + kzt_guest_registry_t *registry, + kzt_guest_registry_diagnostics_t *diagnostics); + +int kzt_guest_registry_configure_diagnostics( + kzt_guest_registry_t *registry, + const kzt_guest_registry_diagnostic_config_t *config); + +int kzt_guest_registry_get_diagnostic_report( + kzt_guest_registry_t *registry, + kzt_guest_registry_diagnostic_report_t *report); + +int kzt_guest_registry_note_diagnostic( + kzt_guest_registry_t *registry, + kzt_guest_registry_result_t result, + uintptr_t link_map_addr, + kzt_guest_registry_observation_diagnostic_t *diagnostic); + +int kzt_guest_registry_dump_text( + kzt_guest_registry_t *registry, + kzt_guest_registry_dump_sink_fn sink, + void *opaque); + +void kzt_guest_object_snapshot_free(kzt_guest_object_snapshot_t *snapshot); +void kzt_guest_registry_dump_free(kzt_guest_registry_dump_t *dump); + +#ifdef KZT_GUEST_REGISTRY_TEST +void kzt_guest_registry_test_set_alloc_failure_after(long allocations); +void kzt_guest_registry_test_set_dynamic_commit_failure_after(long commits); +#endif + +#endif diff --git a/target/i386/latx/include/kzt_observation_adapter.h b/target/i386/latx/include/kzt_observation_adapter.h new file mode 100644 index 0000000000..aff7f5904a --- /dev/null +++ b/target/i386/latx/include/kzt_observation_adapter.h @@ -0,0 +1,66 @@ +#ifndef KZT_OBSERVATION_ADAPTER_H +#define KZT_OBSERVATION_ADAPTER_H + +#include +#include + +#include "kzt_guest_link_map_reader.h" +#include "kzt_guest_dynamic.h" +#include "kzt_guest_registry.h" + +typedef enum kzt_observation_adapter_result { + KZT_OBSERVATION_ADAPTER_DISABLED = 0, + KZT_OBSERVATION_ADAPTER_ADDED, + KZT_OBSERVATION_ADAPTER_UNCHANGED, + KZT_OBSERVATION_ADAPTER_UPDATED, + KZT_OBSERVATION_ADAPTER_CONFLICT, + KZT_OBSERVATION_ADAPTER_READER_FAILED, + KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED, +} kzt_observation_adapter_result_t; + +typedef int (*kzt_observation_legacy_flow_fn)(uintptr_t link_map_addr, + void *opaque); + +typedef struct kzt_observation_adapter_dynamic_diagnostic { + int attempted; + int parse_return; + uintptr_t dynamic_addr; + kzt_guest_dynamic_status_t status; + kzt_guest_dynamic_error_t error; + size_t entry_count; + uintptr_t read_error_addr; + int commit_attempted; + kzt_guest_registry_result_t commit_result; + kzt_guest_registry_observation_diagnostic_t registry; +} kzt_observation_adapter_dynamic_diagnostic_t; + +typedef struct kzt_observation_adapter_diagnostic { + int enabled; + int emitted; + kzt_observation_adapter_result_t result; + uintptr_t link_map_addr; + kzt_guest_registry_observation_diagnostic_t registry; + kzt_observation_adapter_dynamic_diagnostic_t dynamic; +} kzt_observation_adapter_diagnostic_t; + +typedef void (*kzt_observation_adapter_diagnostic_fn)( + const kzt_observation_adapter_diagnostic_t *diagnostic, + void *opaque); + +typedef struct kzt_observation_adapter_request { + int enabled; + int diagnostics_enabled; + uintptr_t link_map_addr; + kzt_guest_registry_t *registry; + const kzt_guest_link_map_reader_ops_t *reader_ops; + kzt_observation_legacy_flow_fn legacy_flow; + void *legacy_opaque; + kzt_observation_adapter_diagnostic_fn diagnostic; + void *diagnostic_opaque; +} kzt_observation_adapter_request_t; + +int kzt_observe_guest_object_from_callback( + const kzt_observation_adapter_request_t *request, + kzt_observation_adapter_result_t *result); + +#endif diff --git a/target/i386/latx/include/latx-options.h b/target/i386/latx/include/latx-options.h index bbe3756c17..74b69ae0ba 100644 --- a/target/i386/latx/include/latx-options.h +++ b/target/i386/latx/include/latx-options.h @@ -125,7 +125,9 @@ extern unsigned long long counter_mips_tr; #if defined(CONFIG_LATX) && defined(CONFIG_LATX_KZT) #define ENVSUP_KZT \ - ENVFUN(LATX_KZT, handle_arg_latx_kzt) + ENVFUN(LATX_KZT, handle_arg_latx_kzt) \ + ENVFUN(LATX_KZT_REGISTRY_DIAGNOSTICS, \ + handle_arg_latx_kzt_registry_diagnostics) #else #define ENVSUP_KZT #endif diff --git a/tests/unit/kzt/test_guest_dynamic_diagnostics.c b/tests/unit/kzt/test_guest_dynamic_diagnostics.c new file mode 100644 index 0000000000..9ce831257e --- /dev/null +++ b/tests/unit/kzt/test_guest_dynamic_diagnostics.c @@ -0,0 +1,496 @@ +#include +#include + +#include "target/i386/latx/include/kzt_guest_dynamic_diagnostics.h" + +#define TEST_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define KZT_TEST_UNKNOWN_DYNAMIC_TAG 0x6000000d + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_size(const char *name, size_t got, size_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, got, expected); + ++failures; +} + +static void check_u64(const char *name, uint64_t got, uint64_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%llx expected 0x%llx\n", name, + (unsigned long long)got, (unsigned long long)expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_str_contains(const char *name, const char *value, + const char *expected) +{ + if (value && expected && strstr(value, expected)) { + return; + } + + fprintf(stderr, "%s: '%s' does not contain '%s'\n", name, + value ? value : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static kzt_guest_dynamic_field_t make_field( + uint64_t value, + kzt_guest_dynamic_address_semantics_t semantics) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = semantics, + }; +} + +static kzt_guest_dynamic_parse_result_t make_complete_result(void) +{ + kzt_guest_dynamic_parse_result_t result = { + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .error = KZT_GUEST_DYNAMIC_ERROR_NONE, + .entry_count = 12, + .scan_limit = KZT_GUEST_DYNAMIC_SCAN_LIMIT, + .view = { + .dynamic_addr = 0x7000001000, + .load_bias = 0x7000000000, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 12, + .has_null = 1, + .scan_limit = KZT_GUEST_DYNAMIC_SCAN_LIMIT, + .symtab = make_field(0x7000010000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .strtab = make_field(0x7000020000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .syment = make_field(24, KZT_GUEST_DYNAMIC_SCALAR), + .strsz = make_field(0x220, KZT_GUEST_DYNAMIC_SCALAR), + .hash = make_field(0x7000030000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .gnu_hash = make_field(0x7000040000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .versym = make_field(0x7000050000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .verneed = make_field(0x7000060000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .verneednum = make_field(2, KZT_GUEST_DYNAMIC_SCALAR), + .verdef = make_field(0x7000070000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .verdefnum = make_field(1, KZT_GUEST_DYNAMIC_SCALAR), + .rela = make_field(0x7000080000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .relasz = make_field(0x60, KZT_GUEST_DYNAMIC_SCALAR), + .relaent = make_field(24, KZT_GUEST_DYNAMIC_SCALAR), + .rel = make_field(0x7000090000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .relsz = make_field(0x40, KZT_GUEST_DYNAMIC_SCALAR), + .relent = make_field(16, KZT_GUEST_DYNAMIC_SCALAR), + .jmprel = make_field(0x70000a0000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .pltrelsz = make_field(0x30, KZT_GUEST_DYNAMIC_SCALAR), + .pltrel = make_field(DT_RELA, KZT_GUEST_DYNAMIC_SCALAR), + .pltgot = make_field(0x70000b0000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .needed_offsets = { 0x10, 0x38 }, + .needed_count = 2, + .needed_address_semantics = + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET, + }, + }; + + return result; +} + +static const kzt_guest_dynamic_diagnostic_field_t *require_field( + const kzt_guest_dynamic_diagnostic_report_t *report, + const char *name) +{ + const kzt_guest_dynamic_diagnostic_field_t *field = + kzt_guest_dynamic_diagnostic_find_field(report, name); + + check_true(name, field != NULL); + return field; +} + +static void assert_field_match( + const kzt_guest_dynamic_diagnostic_report_t *report, + const char *name, + kzt_guest_dynamic_diagnostic_match_t expected) +{ + const kzt_guest_dynamic_diagnostic_field_t *field = require_field(report, + name); + + if (!field) { + return; + } + + check_int(name, field->match, expected); +} + +static void test_identical_views_are_matched(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + + check_int("identical.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("identical.status", report.status_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_int("identical.entry-count", report.entry_count_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_int("identical.unknown-tags", report.unknown_tags_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_size("identical.field-count", report.field_count, 22); + check_size("identical.matched", report.matched_count, report.field_count); + check_size("identical.missing-old", report.missing_old_count, 0); + check_size("identical.missing-new", report.missing_new_count, 0); + check_size("identical.mismatch", report.mismatch_count, 0); + check_size("identical.difference", report.difference_count, 0); + check_size("identical.blocking", report.blocking_count, 0); + assert_field_match(&report, "symtab", + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + assert_field_match(&report, "needed_offsets", + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); +} + +static void test_old_field_missing_from_new_is_reported(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + const kzt_guest_dynamic_diagnostic_field_t *field; + + memset(&new_result.view.strtab, 0, sizeof(new_result.view.strtab)); + check_int("missing-new.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + + field = require_field(&report, "strtab"); + if (!field) { + return; + } + + check_int("missing-new.match", field->match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISSING_NEW); + check_true("missing-new.old-present", field->old_present); + check_true("missing-new.new-present", !field->new_present); + check_u64("missing-new.old-value", field->old_value, 0x7000020000); + check_size("missing-new.count", report.missing_new_count, 1); + check_size("missing-new.difference", report.difference_count, 1); + check_size("missing-new.blocking", report.blocking_count, 0); +} + +static void test_new_parser_incomplete_states_are_reported(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + + new_result.status = KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + new_result.error = KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED; + new_result.entry_count = KZT_GUEST_DYNAMIC_SCAN_LIMIT; + new_result.view.status = KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL; + new_result.view.entry_count = KZT_GUEST_DYNAMIC_SCAN_LIMIT; + new_result.view.has_null = 0; + + check_int("truncated.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("truncated.status-match", report.status_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_int("truncated.new-status", report.new_status, + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL); + check_int("truncated.new-error", report.new_error, + KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED); + check_true("truncated.flag", report.new_truncated); + check_size("truncated.blocking", report.blocking_count, 1); + + new_result = make_complete_result(); + new_result.status = KZT_GUEST_DYNAMIC_READ_ERROR; + new_result.error = KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE; + new_result.read_error_addr = 0x7000001080; + new_result.entry_count = 8; + new_result.view.status = KZT_GUEST_DYNAMIC_READ_ERROR; + new_result.view.entry_count = 8; + new_result.view.has_null = 0; + + check_int("read-error.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("read-error.status-match", report.status_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_int("read-error.new-status", report.new_status, + KZT_GUEST_DYNAMIC_READ_ERROR); + check_int("read-error.new-error", report.new_error, + KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE); + check_true("read-error.flag", report.new_read_error); + check_uintptr("read-error.addr", report.new_read_error_addr, + 0x7000001080); + check_size("read-error.blocking", report.blocking_count, 1); +} + +static void test_unknown_tag_difference_is_diagnostic_only(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + kzt_guest_dynamic_diagnostic_summary_t summary; + char line[512]; + + new_result.unknown_tag_count = 1; + new_result.first_unknown_tag = KZT_TEST_UNKNOWN_DYNAMIC_TAG; + new_result.first_unknown_tag_index = 3; + new_result.view.unknown_tag_count = 1; + new_result.view.first_unknown_tag = KZT_TEST_UNKNOWN_DYNAMIC_TAG; + new_result.view.first_unknown_tag_index = 3; + + check_int("unknown.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("unknown.match", report.unknown_tags_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_size("unknown.old-count", report.old_unknown_tag_count, 0); + check_size("unknown.new-count", report.new_unknown_tag_count, 1); + check_size("unknown.field-mismatch", report.mismatch_count, 0); + check_size("unknown.difference", report.difference_count, 1); + check_size("unknown.blocking", report.blocking_count, 0); + + check_int("unknown.summary", + kzt_guest_dynamic_diagnostics_summarize(&report, 0xabc000, 9, + &summary), + 0); + check_int("unknown.summary-kind", summary.first_difference_kind, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_UNKNOWN_TAGS); + check_true("unknown.summary-new-present", summary.first_new_present); + check_true("unknown.summary-new-tag", + summary.first_new_tag == KZT_TEST_UNKNOWN_DYNAMIC_TAG); + check_size("unknown.summary-new-index", summary.first_new_tag_index, 3); + check_int("unknown.format", + kzt_guest_dynamic_diagnostics_format_summary(&summary, line, + sizeof(line)), 0); + check_str_contains("unknown.format-first", line, "first=unknown_tags"); +} + +static void test_needed_offsets_difference_is_reported(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + const kzt_guest_dynamic_diagnostic_field_t *field; + + new_result.view.needed_offsets[1] = 0x58; + check_int("needed.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + + field = require_field(&report, "needed_offsets"); + if (!field) { + return; + } + + check_int("needed.match", field->match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_size("needed.old-count", field->old_count, 2); + check_size("needed.new-count", field->new_count, 2); + check_u64("needed.old-first", field->old_value, 0x10); + check_u64("needed.new-first", field->new_value, 0x10); + check_size("needed.mismatch", report.mismatch_count, 1); + check_size("needed.difference", report.difference_count, 1); + check_size("needed.blocking", report.blocking_count, 0); +} + +static void test_error_status_is_blocking(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + kzt_guest_dynamic_diagnostic_summary_t summary; + char line[512]; + + old_result.status = KZT_GUEST_DYNAMIC_ERROR; + old_result.error = KZT_GUEST_DYNAMIC_ERROR_INVALID_ARGUMENT; + old_result.view.status = KZT_GUEST_DYNAMIC_ERROR; + new_result.status = KZT_GUEST_DYNAMIC_ERROR; + new_result.error = KZT_GUEST_DYNAMIC_ERROR_INVALID_ARGUMENT; + new_result.view.status = KZT_GUEST_DYNAMIC_ERROR; + + check_int("error.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("error.status-match", report.status_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_size("error.difference", report.difference_count, 0); + check_size("error.blocking", report.blocking_count, 2); + + check_int("error.summary", + kzt_guest_dynamic_diagnostics_summarize(&report, 0xabc100, 10, + &summary), + 0); + check_true("error.summary-blocking", summary.blocking); + check_int("error.summary-kind", summary.first_difference_kind, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_STATUS); + check_int("error.summary-match", summary.first_difference_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MATCHED); + check_int("error.format", + kzt_guest_dynamic_diagnostics_format_summary(&summary, line, + sizeof(line)), 0); + check_str_contains("error.format-blocking", line, "blocking=1"); + check_str_contains("error.format-first", line, "first=status"); +} + +static void test_summary_includes_identity_and_first_field(void) +{ + kzt_guest_dynamic_parse_result_t old_result = make_complete_result(); + kzt_guest_dynamic_parse_result_t new_result = make_complete_result(); + kzt_guest_dynamic_diagnostic_report_t report; + kzt_guest_dynamic_diagnostic_summary_t summary; + char line[512]; + + new_result.view.pltgot.value = 0x70000c0000; + check_int("summary.compare", + kzt_guest_dynamic_diagnostics_compare(&old_result, &new_result, + &report), + 0); + check_int("summary.create", + kzt_guest_dynamic_diagnostics_summarize(&report, 0xabcdef00, + 17, &summary), + 0); + + check_uintptr("summary.link-map", summary.link_map_addr, 0xabcdef00); + check_ulong("summary.generation", summary.generation, 17); + check_true("summary.not-matched", !summary.matched); + check_true("summary.not-blocking", !summary.blocking); + check_size("summary.difference", summary.difference_count, 1); + check_int("summary.kind", summary.first_difference_kind, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_DIFFERENCE_FIELD); + check_true("summary.name", !strcmp(summary.first_difference_name, + "pltgot")); + check_int("summary.match", summary.first_difference_match, + KZT_GUEST_DYNAMIC_DIAGNOSTIC_MISMATCH); + check_true("summary.old-present", summary.first_old_present); + check_true("summary.new-present", summary.first_new_present); + check_u64("summary.old-value", summary.first_old_value, 0x70000b0000); + check_u64("summary.new-value", summary.first_new_value, 0x70000c0000); + + check_int("summary.format", + kzt_guest_dynamic_diagnostics_format_summary(&summary, line, + sizeof(line)), 0); + check_str_contains("summary.format-object", line, + "link_map=0xabcdef00"); + check_str_contains("summary.format-generation", line, + "generation=17"); + check_str_contains("summary.format-first", line, "first=pltgot"); + check_str_contains("summary.format-differences", line, + "differences=1"); +} + +static int test_matches_filter(const char *name, int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--filter") && i + 1 < argc) { + return strcmp(name, argv[i + 1]) == 0; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + if (test_matches_filter("identical_views_are_matched", argc, argv)) { + test_identical_views_are_matched(); + } + if (test_matches_filter("old_field_missing_from_new_is_reported", + argc, argv)) { + test_old_field_missing_from_new_is_reported(); + } + if (test_matches_filter("new_parser_incomplete_states_are_reported", + argc, argv)) { + test_new_parser_incomplete_states_are_reported(); + } + if (test_matches_filter("unknown_tag_difference_is_diagnostic_only", + argc, argv)) { + test_unknown_tag_difference_is_diagnostic_only(); + } + if (test_matches_filter("needed_offsets_difference_is_reported", + argc, argv)) { + test_needed_offsets_difference_is_reported(); + } + if (test_matches_filter("error_status_is_blocking", argc, argv)) { + test_error_status_is_blocking(); + } + if (test_matches_filter("summary_includes_identity_and_first_field", + argc, argv)) { + test_summary_includes_identity_and_first_field(); + } + + if (failures) { + fprintf(stderr, "kzt-guest-dynamic-diagnostics: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-guest-dynamic-diagnostics: selected contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_dynamic_parser.c b/tests/unit/kzt/test_guest_dynamic_parser.c new file mode 100644 index 0000000000..816219ddb2 --- /dev/null +++ b/tests/unit/kzt/test_guest_dynamic_parser.c @@ -0,0 +1,450 @@ +#include +#include + +#include "target/i386/latx/include/kzt_guest_dynamic.h" + +#define TEST_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define KZT_TEST_UNKNOWN_DYNAMIC_TAG 0x6000000d + +typedef struct fake_dynamic_memory { + uintptr_t base; + size_t size; + int read_calls; +} fake_dynamic_memory_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_size(const char *name, size_t got, size_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_u64(const char *name, uint64_t got, uint64_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%llx expected 0x%llx\n", name, + (unsigned long long)got, (unsigned long long)expected); + ++failures; +} + +static void check_i64(const char *name, int64_t got, int64_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lld expected %lld\n", name, + (long long)got, (long long)expected); + ++failures; +} + +static int fake_read_memory(uintptr_t guest_addr, void *dst, size_t size, + void *opaque) +{ + fake_dynamic_memory_t *memory = opaque; + + ++memory->read_calls; + if (guest_addr < memory->base || + size > memory->size || + guest_addr - memory->base > memory->size - size) { + return -1; + } + + memcpy(dst, (const void *)guest_addr, size); + return 0; +} + +static kzt_guest_link_map_reader_ops_t fake_ops(fake_dynamic_memory_t *memory) +{ + kzt_guest_link_map_reader_ops_t ops = { + .read_memory = fake_read_memory, + .opaque = memory, + }; + + return ops; +} + +static void check_field(const char *name, + const kzt_guest_dynamic_field_t *field, + uint64_t value, + kzt_guest_dynamic_address_semantics_t semantics) +{ + check_true(name, field->present); + check_u64(name, field->value, value); + check_int(name, field->address_semantics, semantics); +} + +static void test_complete_runtime_dynamic_view(void) +{ + Elf64_Dyn dynamic[] = { + { .d_tag = DT_NEEDED, .d_un.d_val = 0x10 }, + { .d_tag = DT_NEEDED, .d_un.d_val = 0x38 }, + { .d_tag = DT_SYMTAB, .d_un.d_ptr = 0x7000010000 }, + { .d_tag = DT_STRTAB, .d_un.d_ptr = 0x7000020000 }, + { .d_tag = DT_SYMENT, .d_un.d_val = sizeof(Elf64_Sym) }, + { .d_tag = DT_STRSZ, .d_un.d_val = 0x220 }, + { .d_tag = DT_HASH, .d_un.d_ptr = 0x7000030000 }, + { .d_tag = DT_GNU_HASH, .d_un.d_ptr = 0x7000040000 }, + { .d_tag = DT_VERSYM, .d_un.d_ptr = 0x7000050000 }, + { .d_tag = DT_VERNEED, .d_un.d_ptr = 0x7000060000 }, + { .d_tag = DT_VERNEEDNUM, .d_un.d_val = 2 }, + { .d_tag = DT_VERDEF, .d_un.d_ptr = 0x7000070000 }, + { .d_tag = DT_VERDEFNUM, .d_un.d_val = 1 }, + { .d_tag = DT_RELA, .d_un.d_ptr = 0x7000080000 }, + { .d_tag = DT_RELASZ, .d_un.d_val = 0x60 }, + { .d_tag = DT_RELAENT, .d_un.d_val = sizeof(Elf64_Rela) }, + { .d_tag = DT_REL, .d_un.d_ptr = 0x7000090000 }, + { .d_tag = DT_RELSZ, .d_un.d_val = 0x40 }, + { .d_tag = DT_RELENT, .d_un.d_val = sizeof(Elf64_Rel) }, + { .d_tag = DT_JMPREL, .d_un.d_ptr = 0x70000a0000 }, + { .d_tag = DT_PLTRELSZ, .d_un.d_val = 0x30 }, + { .d_tag = DT_PLTREL, .d_un.d_val = DT_RELA }, + { .d_tag = DT_PLTGOT, .d_un.d_ptr = 0x70000b0000 }, + { .d_tag = DT_NULL, .d_un.d_val = 0 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + + check_int("dynamic.complete.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x400000, + &ops, &result), + 0); + check_int("dynamic.complete.status", result.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_int("dynamic.complete.view-status", result.view.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_true("dynamic.complete.has-null", result.view.has_null); + check_size("dynamic.complete.entry-count", result.entry_count, + TEST_ARRAY_SIZE(dynamic) - 1); + check_size("dynamic.complete.view-entry-count", result.view.entry_count, + TEST_ARRAY_SIZE(dynamic) - 1); + check_int("dynamic.complete.reader-calls", memory.read_calls, + TEST_ARRAY_SIZE(dynamic)); + + check_field("dynamic.symtab", &result.view.symtab, 0x7000010000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.strtab", &result.view.strtab, 0x7000020000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.syment", &result.view.syment, sizeof(Elf64_Sym), + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.strsz", &result.view.strsz, 0x220, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.hash", &result.view.hash, 0x7000030000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.gnu-hash", &result.view.gnu_hash, 0x7000040000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.versym", &result.view.versym, 0x7000050000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.verneed", &result.view.verneed, 0x7000060000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.verneednum", &result.view.verneednum, 2, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.verdef", &result.view.verdef, 0x7000070000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.verdefnum", &result.view.verdefnum, 1, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.rela", &result.view.rela, 0x7000080000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.relasz", &result.view.relasz, 0x60, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.relaent", &result.view.relaent, sizeof(Elf64_Rela), + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.rel", &result.view.rel, 0x7000090000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.relsz", &result.view.relsz, 0x40, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.relent", &result.view.relent, sizeof(Elf64_Rel), + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.jmprel", &result.view.jmprel, 0x70000a0000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("dynamic.pltrelsz", &result.view.pltrelsz, 0x30, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.pltrel", &result.view.pltrel, DT_RELA, + KZT_GUEST_DYNAMIC_SCALAR); + check_field("dynamic.pltgot", &result.view.pltgot, 0x70000b0000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + + check_size("dynamic.needed.count", result.view.needed_count, 2); + check_int("dynamic.needed.semantics", + result.view.needed_address_semantics, + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET); + check_u64("dynamic.needed.0", result.view.needed_offsets[0], 0x10); + check_u64("dynamic.needed.1", result.view.needed_offsets[1], 0x38); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_dynamic_address_semantics(void) +{ + Elf64_Dyn dynamic[] = { + { .d_tag = DT_SYMTAB, .d_un.d_ptr = 0x5000010000 }, + { .d_tag = DT_STRTAB, .d_un.d_ptr = 0x5000020000 }, + { .d_tag = DT_JMPREL, .d_un.d_ptr = 0x5000030000 }, + { .d_tag = DT_PLTGOT, .d_un.d_ptr = 0x5000040000 }, + { .d_tag = DT_NEEDED, .d_un.d_val = 0x84 }, + { .d_tag = DT_NULL, .d_un.d_val = 0 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + + check_int("semantics.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("semantics.status", result.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_field("semantics.symtab", &result.view.symtab, 0x5000010000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("semantics.strtab", &result.view.strtab, 0x5000020000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("semantics.jmprel", &result.view.jmprel, 0x5000030000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_field("semantics.pltgot", &result.view.pltgot, 0x5000040000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_size("semantics.needed.count", result.view.needed_count, 1); + check_int("semantics.needed.semantics", + result.view.needed_address_semantics, + KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET); + check_u64("semantics.needed.offset", result.view.needed_offsets[0], + 0x84); + check_u64("semantics.load-bias-preserved", result.view.load_bias, + 0x100000); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_read_failure_reports_parser_state(void) +{ + Elf64_Dyn dynamic[] = { + { .d_tag = DT_SYMTAB, .d_un.d_ptr = 0x5000010000 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + uintptr_t expected_error_addr = (uintptr_t)&dynamic[1]; + + check_int("read-failure.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("read-failure.status", result.status, + KZT_GUEST_DYNAMIC_READ_ERROR); + check_int("read-failure.view-status", result.view.status, + KZT_GUEST_DYNAMIC_READ_ERROR); + check_int("read-failure.error", result.error, + KZT_GUEST_DYNAMIC_ERROR_READ_FAILURE); + check_size("read-failure.entry-count", result.entry_count, 1); + check_size("read-failure.view-entry-count", result.view.entry_count, 1); + check_u64("read-failure.addr", result.read_error_addr, + expected_error_addr); + check_size("read-failure.scan-limit", result.scan_limit, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_size("read-failure.view-scan-limit", result.view.scan_limit, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_int("read-failure.reader-calls", memory.read_calls, 2); + check_true("read-failure.no-null", !result.view.has_null); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_missing_null_stops_at_scan_limit(void) +{ + Elf64_Dyn dynamic[KZT_GUEST_DYNAMIC_SCAN_LIMIT]; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + size_t i; + + for (i = 0; i < TEST_ARRAY_SIZE(dynamic); ++i) { + dynamic[i].d_tag = DT_SYMENT; + dynamic[i].d_un.d_val = sizeof(Elf64_Sym); + } + + check_int("scan-limit.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("scan-limit.status", result.status, + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL); + check_int("scan-limit.view-status", result.view.status, + KZT_GUEST_DYNAMIC_TRUNCATED_NO_NULL); + check_int("scan-limit.error", result.error, + KZT_GUEST_DYNAMIC_ERROR_SCAN_LIMIT_EXCEEDED); + check_size("scan-limit.entry-count", result.entry_count, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_size("scan-limit.view-entry-count", result.view.entry_count, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_size("scan-limit.scan-limit", result.scan_limit, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_int("scan-limit.reader-calls", memory.read_calls, + KZT_GUEST_DYNAMIC_SCAN_LIMIT); + check_true("scan-limit.no-null", !result.view.has_null); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_unknown_tag_is_diagnostic_only(void) +{ + Elf64_Dyn dynamic[] = { + { .d_tag = KZT_TEST_UNKNOWN_DYNAMIC_TAG, .d_un.d_val = 0x44 }, + { .d_tag = DT_STRTAB, .d_un.d_ptr = 0x5000020000 }, + { .d_tag = DT_NULL, .d_un.d_val = 0 }, + }; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + + check_int("unknown-tag.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("unknown-tag.status", result.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_int("unknown-tag.error", result.error, + KZT_GUEST_DYNAMIC_ERROR_NONE); + check_size("unknown-tag.count", result.unknown_tag_count, 1); + check_size("unknown-tag.view-count", result.view.unknown_tag_count, 1); + check_i64("unknown-tag.first", result.first_unknown_tag, + KZT_TEST_UNKNOWN_DYNAMIC_TAG); + check_i64("unknown-tag.view-first", result.view.first_unknown_tag, + KZT_TEST_UNKNOWN_DYNAMIC_TAG); + check_size("unknown-tag.index", result.first_unknown_tag_index, 0); + check_size("unknown-tag.view-index", result.view.first_unknown_tag_index, + 0); + check_field("unknown-tag.strtab", &result.view.strtab, 0x5000020000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_true("unknown-tag.has-null", result.view.has_null); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static void test_too_many_needed_reports_resource_limit(void) +{ + Elf64_Dyn dynamic[KZT_GUEST_DYNAMIC_NEEDED_LIMIT + 2]; + fake_dynamic_memory_t memory = { + .base = (uintptr_t)dynamic, + .size = sizeof(dynamic), + }; + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_dynamic_parse_result_t result = { 0 }; + size_t i; + + for (i = 0; i < KZT_GUEST_DYNAMIC_NEEDED_LIMIT + 1; ++i) { + dynamic[i].d_tag = DT_NEEDED; + dynamic[i].d_un.d_val = i * 0x10; + } + dynamic[KZT_GUEST_DYNAMIC_NEEDED_LIMIT + 1].d_tag = DT_NULL; + dynamic[KZT_GUEST_DYNAMIC_NEEDED_LIMIT + 1].d_un.d_val = 0; + + check_int("needed-limit.parse", + kzt_guest_dynamic_parse((uintptr_t)dynamic, 0x100000, + &ops, &result), + 0); + check_int("needed-limit.status", result.status, + KZT_GUEST_DYNAMIC_ERROR); + check_int("needed-limit.view-status", result.view.status, + KZT_GUEST_DYNAMIC_ERROR); + check_int("needed-limit.error", result.error, + KZT_GUEST_DYNAMIC_ERROR_TOO_MANY_NEEDED); + check_size("needed-limit.entry-count", result.entry_count, + KZT_GUEST_DYNAMIC_NEEDED_LIMIT); + check_size("needed-limit.view-entry-count", result.view.entry_count, + KZT_GUEST_DYNAMIC_NEEDED_LIMIT); + check_size("needed-limit.needed-count", result.view.needed_count, + KZT_GUEST_DYNAMIC_NEEDED_LIMIT); + check_true("needed-limit.no-null", !result.view.has_null); + + kzt_guest_dynamic_parse_result_clear(&result); +} + +static int test_matches_filter(const char *name, int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--filter") && i + 1 < argc) { + return strcmp(name, argv[i + 1]) == 0; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + if (test_matches_filter("complete_runtime_dynamic_view", argc, argv)) { + test_complete_runtime_dynamic_view(); + } + if (test_matches_filter("dynamic_address_semantics", argc, argv)) { + test_dynamic_address_semantics(); + } + if (test_matches_filter("read_failure_reports_parser_state", + argc, argv)) { + test_read_failure_reports_parser_state(); + } + if (test_matches_filter("missing_null_stops_at_scan_limit", argc, argv)) { + test_missing_null_stops_at_scan_limit(); + } + if (test_matches_filter("unknown_tag_is_diagnostic_only", argc, argv)) { + test_unknown_tag_is_diagnostic_only(); + } + if (test_matches_filter("too_many_needed_reports_resource_limit", + argc, argv)) { + test_too_many_needed_reports_resource_limit(); + } + + if (failures) { + fprintf(stderr, "kzt-guest-dynamic-parser: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-guest-dynamic-parser: selected contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_dynamic_snapshot.c b/tests/unit/kzt/test_guest_dynamic_snapshot.c new file mode 100644 index 0000000000..acf5533c75 --- /dev/null +++ b/tests/unit/kzt/test_guest_dynamic_snapshot.c @@ -0,0 +1,411 @@ +#include +#include + +#include "target/i386/latx/include/kzt_guest_registry.h" + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_u64(const char *name, uint64_t got, uint64_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%llx expected 0x%llx\n", name, + (unsigned long long)got, (unsigned long long)expected); + ++failures; +} + +static kzt_guest_object_observation_t make_observation(uintptr_t link_map_addr) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { 0x100000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x101000, KZT_GUEST_FIELD_OK }, + .map_start = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .path = { "/guest/libfoo.so", KZT_GUEST_FIELD_OK }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_dynamic_field_t make_field( + uint64_t value, + kzt_guest_dynamic_address_semantics_t semantics) +{ + return (kzt_guest_dynamic_field_t) { + .present = 1, + .value = value, + .address_semantics = semantics, + }; +} + +static kzt_guest_dynamic_view_t make_dynamic_view(uintptr_t dynamic_addr, + uintptr_t load_bias, + uint64_t symtab) +{ + kzt_guest_dynamic_view_t view = { + .dynamic_addr = dynamic_addr, + .load_bias = load_bias, + .status = KZT_GUEST_DYNAMIC_COMPLETE, + .entry_count = 8, + .has_null = 1, + .scan_limit = KZT_GUEST_DYNAMIC_SCAN_LIMIT, + .unknown_tag_count = 1, + .first_unknown_tag = 0x6000000d, + .first_unknown_tag_index = 3, + .symtab = make_field(symtab, KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .strtab = make_field(symtab + 0x1000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .strsz = make_field(0x240, KZT_GUEST_DYNAMIC_SCALAR), + .gnu_hash = make_field(symtab + 0x2000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .versym = make_field(symtab + 0x3000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .jmprel = make_field(symtab + 0x4000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .pltrelsz = make_field(0x30, KZT_GUEST_DYNAMIC_SCALAR), + .pltgot = make_field(symtab + 0x5000, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS), + .needed_offsets = { 0x10, 0x38 }, + .needed_count = 2, + .needed_address_semantics = KZT_GUEST_DYNAMIC_STRING_TABLE_OFFSET, + }; + + return view; +} + +static int dynamic_field_equal(const kzt_guest_dynamic_field_t *left, + const kzt_guest_dynamic_field_t *right) +{ + return left->present == right->present && + left->value == right->value && + left->address_semantics == right->address_semantics; +} + +static void assert_dynamic_view_equal(const char *name, + const kzt_guest_dynamic_view_t *got, + const kzt_guest_dynamic_view_t *expected) +{ + char field_name[128]; + size_t i; + + check_uintptr(name, got->dynamic_addr, expected->dynamic_addr); + check_uintptr(name, got->load_bias, expected->load_bias); + check_int(name, got->status, expected->status); + check_ulong(name, got->entry_count, expected->entry_count); + check_int(name, got->has_null, expected->has_null); + check_ulong("dynamic.scan_limit", got->scan_limit, expected->scan_limit); + check_ulong("dynamic.unknown_tag_count", got->unknown_tag_count, + expected->unknown_tag_count); + check_ulong("dynamic.first_unknown_tag", + (unsigned long)got->first_unknown_tag, + (unsigned long)expected->first_unknown_tag); + check_ulong("dynamic.first_unknown_tag_index", + got->first_unknown_tag_index, + expected->first_unknown_tag_index); + check_true("dynamic.symtab", dynamic_field_equal(&got->symtab, + &expected->symtab)); + check_true("dynamic.strtab", dynamic_field_equal(&got->strtab, + &expected->strtab)); + check_true("dynamic.strsz", dynamic_field_equal(&got->strsz, + &expected->strsz)); + check_true("dynamic.gnu_hash", dynamic_field_equal(&got->gnu_hash, + &expected->gnu_hash)); + check_true("dynamic.versym", dynamic_field_equal(&got->versym, + &expected->versym)); + check_true("dynamic.jmprel", dynamic_field_equal(&got->jmprel, + &expected->jmprel)); + check_true("dynamic.pltrelsz", dynamic_field_equal(&got->pltrelsz, + &expected->pltrelsz)); + check_true("dynamic.pltgot", dynamic_field_equal(&got->pltgot, + &expected->pltgot)); + check_ulong("dynamic.needed_count", got->needed_count, + expected->needed_count); + check_int("dynamic.needed_semantics", got->needed_address_semantics, + expected->needed_address_semantics); + for (i = 0; i < expected->needed_count; ++i) { + snprintf(field_name, sizeof(field_name), "%s.needed[%lu]", name, + (unsigned long)i); + check_u64(field_name, got->needed_offsets[i], + expected->needed_offsets[i]); + } +} + +static kzt_guest_object_snapshot_t *find_snapshot( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr) +{ + kzt_guest_object_snapshot_t *snapshot = NULL; + + check_int("find_by_link_map", kzt_guest_registry_find_by_link_map( + registry, link_map_addr, &snapshot), 0); + check_true("find_by_link_map.snapshot", snapshot != NULL); + return snapshot; +} + +static void test_commit_and_query_are_per_object(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t first = make_observation(0x1000); + kzt_guest_object_observation_t second = make_observation(0x2000); + kzt_guest_dynamic_view_t first_view = + make_dynamic_view(0x101000, 0x100000, 0x7000010000); + kzt_guest_dynamic_view_t queried = { 0 }; + kzt_guest_field_status_t queried_status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long generation = 0; + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + second.dynamic_addr.value = 0x201000; + check_int("observe.first", kzt_guest_registry_observe(registry, &first), + KZT_GUEST_REGISTRY_ADDED); + check_int("observe.second", kzt_guest_registry_observe(registry, &second), + KZT_GUEST_REGISTRY_ADDED); + + check_int("commit.first", + kzt_guest_registry_commit_dynamic_view(registry, 0x1000, + &first_view), + KZT_GUEST_REGISTRY_UPDATED); + + snapshot = find_snapshot(registry, 0x1000); + check_int("snapshot.first.status", snapshot->dynamic_view_status, + KZT_GUEST_FIELD_OK); + check_int("snapshot.first.state", snapshot->state, + KZT_GUEST_OBJECT_PARSED); + check_ulong("snapshot.first.generation", snapshot->generation, 1); + assert_dynamic_view_equal("snapshot.first.view", + &snapshot->dynamic_view, &first_view); + kzt_guest_object_snapshot_free(snapshot); + + snapshot = find_snapshot(registry, 0x2000); + check_int("snapshot.second.status", snapshot->dynamic_view_status, + KZT_GUEST_FIELD_NOT_PARSED); + check_int("snapshot.second.state", snapshot->state, + KZT_GUEST_OBJECT_DISCOVERED); + check_ulong("snapshot.second.generation", snapshot->generation, 2); + check_uintptr("snapshot.second.dynamic-view-zero", + snapshot->dynamic_view.dynamic_addr, 0); + kzt_guest_object_snapshot_free(snapshot); + + check_int("find.dynamic-view", + kzt_guest_registry_find_dynamic_view(registry, 0x1000, + &queried, + &queried_status, + &generation), + 0); + check_int("queried.status", queried_status, KZT_GUEST_FIELD_OK); + check_ulong("queried.generation", generation, 1); + assert_dynamic_view_equal("queried.view", &queried, &first_view); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_repeated_commit_and_replacement_semantics(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x3000); + kzt_guest_dynamic_view_t first_view = + make_dynamic_view(0x301000, 0x300000, 0x7100010000); + kzt_guest_dynamic_view_t replacement = + make_dynamic_view(0x301000, 0x300000, 0x7200010000); + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.object", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("commit.first", + kzt_guest_registry_commit_dynamic_view(registry, 0x3000, + &first_view), + KZT_GUEST_REGISTRY_UPDATED); + check_int("commit.same", + kzt_guest_registry_commit_dynamic_view(registry, 0x3000, + &first_view), + KZT_GUEST_REGISTRY_UNCHANGED); + + replacement.needed_offsets[1] = 0x58; + replacement.entry_count = 9; + replacement.unknown_tag_count = 2; + replacement.first_unknown_tag = 0x6000000e; + replacement.first_unknown_tag_index = 4; + check_int("commit.replacement", + kzt_guest_registry_commit_dynamic_view(registry, 0x3000, + &replacement), + KZT_GUEST_REGISTRY_UPDATED); + + snapshot = find_snapshot(registry, 0x3000); + check_ulong("snapshot.generation-stable", snapshot->generation, 1); + check_int("snapshot.status", snapshot->dynamic_view_status, + KZT_GUEST_FIELD_OK); + check_int("snapshot.state", snapshot->state, KZT_GUEST_OBJECT_PARSED); + assert_dynamic_view_equal("snapshot.replacement", + &snapshot->dynamic_view, &replacement); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_snapshots_survive_replacement_and_registry_destroy(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x4000); + kzt_guest_dynamic_view_t first_view = + make_dynamic_view(0x401000, 0x400000, 0x7300010000); + kzt_guest_dynamic_view_t replacement = + make_dynamic_view(0x401000, 0x400000, 0x7400010000); + kzt_guest_object_snapshot_t *before_replace; + kzt_guest_object_snapshot_t *after_replace; + kzt_guest_registry_dump_t dump = { 0 }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.object", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + check_int("commit.first", + kzt_guest_registry_commit_dynamic_view(registry, 0x4000, + &first_view), + KZT_GUEST_REGISTRY_UPDATED); + before_replace = find_snapshot(registry, 0x4000); + + check_int("commit.replacement", + kzt_guest_registry_commit_dynamic_view(registry, 0x4000, + &replacement), + KZT_GUEST_REGISTRY_UPDATED); + after_replace = find_snapshot(registry, 0x4000); + + check_int("dump.snapshot", + kzt_guest_registry_dump_snapshot(registry, &dump), 0); + check_ulong("dump.count", dump.count, 1); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); + + assert_dynamic_view_equal("before_replace.still-first", + &before_replace->dynamic_view, &first_view); + assert_dynamic_view_equal("after_replace.still-replacement", + &after_replace->dynamic_view, &replacement); + assert_dynamic_view_equal("dump.still-replacement", + &dump.objects[0].dynamic_view, &replacement); + + kzt_guest_object_snapshot_free(before_replace); + kzt_guest_object_snapshot_free(after_replace); + kzt_guest_registry_dump_free(&dump); +} + +static void test_missing_and_destroyed_registry_are_rejected(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_dynamic_view_t view = + make_dynamic_view(0x501000, 0x500000, 0x7500010000); + kzt_guest_dynamic_view_t queried = { + .dynamic_addr = 0xdeadbeef, + }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_OK; + unsigned long generation = 99; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("commit.missing", + kzt_guest_registry_commit_dynamic_view(registry, 0x5000, + &view), + KZT_GUEST_REGISTRY_ERROR); + check_int("find.missing", + kzt_guest_registry_find_dynamic_view(registry, 0x5000, + &queried, &status, + &generation), + -1); + check_uintptr("find.missing.clears-view", queried.dynamic_addr, 0); + check_int("find.missing.status", status, KZT_GUEST_FIELD_NOT_PARSED); + check_ulong("find.missing.generation", generation, 0); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); + check_int("commit.destroyed", + kzt_guest_registry_commit_dynamic_view(registry, 0x5000, + &view), + KZT_GUEST_REGISTRY_DISABLED); + check_int("find.destroyed", + kzt_guest_registry_find_dynamic_view(registry, 0x5000, + &queried, &status, + &generation), + -1); +} + +int main(void) +{ + test_commit_and_query_are_per_object(); + test_repeated_commit_and_replacement_semantics(); + test_snapshots_survive_replacement_and_registry_destroy(); + test_missing_and_destroyed_registry_are_rejected(); + + if (failures) { + fprintf(stderr, "kzt-guest-dynamic-snapshot: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-guest-dynamic-snapshot: all contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_link_map_reader.c b/tests/unit/kzt/test_guest_link_map_reader.c new file mode 100644 index 0000000000..d815d863ef --- /dev/null +++ b/tests/unit/kzt/test_guest_link_map_reader.c @@ -0,0 +1,382 @@ +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/kzt_guest_link_map_reader.h" +#include "target/i386/latx/include/kzt_guest_registry.h" + +typedef struct fake_read_failure { + uintptr_t addr; + size_t size; +} fake_read_failure_t; + +typedef struct fake_reader_memory { + uintptr_t base; + size_t size; + const fake_read_failure_t *failures; + size_t failure_count; +} fake_reader_memory_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_string(const char *name, const char *got, + const char *expected) +{ + if ((!got && !expected) || (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static int ranges_overlap(uintptr_t left_addr, size_t left_size, + uintptr_t right_addr, size_t right_size) +{ + uintptr_t left_end = left_addr + left_size; + uintptr_t right_end = right_addr + right_size; + + return left_addr < right_end && right_addr < left_end; +} + +static int fake_read_memory(uintptr_t guest_addr, void *dst, size_t size, + void *opaque) +{ + fake_reader_memory_t *memory = opaque; + size_t i; + + for (i = 0; i < memory->failure_count; ++i) { + if (ranges_overlap(guest_addr, size, + memory->failures[i].addr, + memory->failures[i].size)) { + return -1; + } + } + + if (guest_addr < memory->base || + size > memory->size || + guest_addr - memory->base > memory->size - size) { + return -1; + } + + memcpy(dst, (const void *)guest_addr, size); + return 0; +} + +static kzt_guest_link_map_reader_ops_t fake_ops(fake_reader_memory_t *memory) +{ + kzt_guest_link_map_reader_ops_t ops = { + .read_memory = fake_read_memory, + .opaque = memory, + }; + + return ops; +} + +static fake_reader_memory_t fake_memory_for(void *base, size_t size, + const fake_read_failure_t *failures, + size_t failure_count) +{ + fake_reader_memory_t memory = { + .base = (uintptr_t)base, + .size = size, + .failures = failures, + .failure_count = failure_count, + }; + + return memory; +} + +static void init_link_map(struct link_map_x64 *link_map, char *name) +{ + memset(link_map, 0, sizeof(*link_map)); + link_map->l_addr = 0x100000; + link_map->l_name = name; + link_map->l_ld = (Elf64_Dyn *)0x101000; + link_map->l_ns = 7; + link_map->l_map_start = 0x100000; + link_map->l_map_end = 0x120000; +} + +static void test_valid_link_map_reads_complete_observation(void) +{ + struct { + struct link_map_x64 link_map; + char guest_name[32]; + } guest = { 0 }; + kzt_guest_object_observation_t observation = { 0 }; + fake_reader_memory_t memory; + kzt_guest_link_map_reader_ops_t ops; + + strcpy(guest.guest_name, "/guest/libfoo.so"); + init_link_map(&guest.link_map, guest.guest_name); + memory = fake_memory_for(&guest, sizeof(guest), NULL, 0); + ops = fake_ops(&memory); + + check_int("read_observation.valid", + kzt_guest_link_map_read_observation((uintptr_t)&guest.link_map, + &ops, + &observation), + 0); + check_uintptr("observation.link_map_addr", + observation.link_map_addr, + (uintptr_t)&guest.link_map); + check_uintptr("observation.load_bias", + observation.load_bias.value, + 0x100000); + check_int("observation.load_bias.status", + observation.load_bias.status, + KZT_GUEST_FIELD_OK); + check_uintptr("observation.dynamic_addr", + observation.dynamic_addr.value, + 0x101000); + check_int("observation.dynamic_addr.status", + observation.dynamic_addr.status, + KZT_GUEST_FIELD_OK); + check_uintptr("observation.map_start", + observation.map_start.value, + 0x100000); + check_uintptr("observation.map_end", + observation.map_end.value, + 0x120000); + check_uintptr("observation.namespace_id", + observation.namespace_id.value, + 7); + check_string("observation.path", observation.path.value, + "/guest/libfoo.so"); + check_int("observation.path.status", + observation.path.status, + KZT_GUEST_FIELD_OK); + check_int("observation.soname.status", + observation.soname.status, + KZT_GUEST_FIELD_NOT_PARSED); + check_int("observation.dynamic_view_status", + observation.dynamic_view_status, + KZT_GUEST_FIELD_NOT_PARSED); + + kzt_guest_link_map_observation_clear(&observation); +} + +static void test_invalid_link_map_is_identity_failure(void) +{ + char unrelated[16] = { 0 }; + kzt_guest_object_observation_t observation = { 0 }; + fake_reader_memory_t memory = fake_memory_for(unrelated, + sizeof(unrelated), + NULL, + 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + + check_int("read_observation.null-link-map", + kzt_guest_link_map_read_observation(0, &ops, &observation), + -1); + check_uintptr("null-link-map.identity", observation.link_map_addr, 0); + + check_int("read_observation.out-of-range-link-map", + kzt_guest_link_map_read_observation((uintptr_t)unrelated - 8, + &ops, + &observation), + -1); + check_uintptr("out-of-range.identity", observation.link_map_addr, 0); +} + +static void test_field_read_failure_forms_partial_observation(void) +{ + struct { + struct link_map_x64 link_map; + char guest_name[32]; + } guest = { 0 }; + fake_read_failure_t read_failures[] = { + { + .addr = (uintptr_t)&guest.link_map + offsetof(struct link_map_x64, l_ld), + .size = sizeof(guest.link_map.l_ld), + }, + { + .addr = (uintptr_t)&guest.link_map + offsetof(struct link_map_x64, l_map_end), + .size = sizeof(guest.link_map.l_map_end), + }, + }; + fake_reader_memory_t memory; + kzt_guest_link_map_reader_ops_t ops; + kzt_guest_object_observation_t observation = { 0 }; + + strcpy(guest.guest_name, "/guest/libpartial.so"); + init_link_map(&guest.link_map, guest.guest_name); + memory = fake_memory_for(&guest, + sizeof(guest), + read_failures, + sizeof(read_failures) / sizeof(read_failures[0])); + ops = fake_ops(&memory); + + check_int("read_observation.partial", + kzt_guest_link_map_read_observation((uintptr_t)&guest.link_map, + &ops, + &observation), + 0); + check_uintptr("partial.identity", + observation.link_map_addr, + (uintptr_t)&guest.link_map); + check_int("partial.load-bias-ok", + observation.load_bias.status, + KZT_GUEST_FIELD_OK); + check_int("partial.dynamic-read-error", + observation.dynamic_addr.status, + KZT_GUEST_FIELD_READ_ERROR); + check_int("partial.map-end-read-error", + observation.map_end.status, + KZT_GUEST_FIELD_READ_ERROR); + check_string("partial.path", observation.path.value, + "/guest/libpartial.so"); + + kzt_guest_link_map_observation_clear(&observation); +} + +static void test_name_snapshot_status_matrix(void) +{ + char storage[128]; + fake_read_failure_t read_failures[] = { + { + .addr = (uintptr_t)(storage + 98), + .size = 1, + }, + }; + fake_reader_memory_t memory; + kzt_guest_link_map_reader_ops_t ops; + kzt_guest_string_field_t name = { 0 }; + + memset(storage, 0, sizeof(storage)); + memcpy(storage, "/guest/libok.so", sizeof("/guest/libok.so")); + storage[32] = 0; + memcpy(storage + 64, "abcd", 4); + memcpy(storage + 96, "broken", sizeof("broken")); + + memory = fake_memory_for(storage, + sizeof(storage), + read_failures, + sizeof(read_failures) / sizeof(read_failures[0])); + ops = fake_ops(&memory); + + check_int("name.valid", + kzt_guest_link_map_read_name_snapshot((uintptr_t)storage, + &ops, + 32, + &name), + 0); + check_int("name.valid.status", name.status, KZT_GUEST_FIELD_OK); + check_string("name.valid.value", name.value, "/guest/libok.so"); + kzt_guest_link_map_string_clear(&name); + + check_int("name.empty", + kzt_guest_link_map_read_name_snapshot((uintptr_t)(storage + 32), + &ops, + 32, + &name), + 0); + check_int("name.empty.status", name.status, KZT_GUEST_FIELD_OK); + check_string("name.empty.value", name.value, ""); + kzt_guest_link_map_string_clear(&name); + + check_int("name.truncated", + kzt_guest_link_map_read_name_snapshot((uintptr_t)(storage + 64), + &ops, + 4, + &name), + 0); + check_int("name.truncated.status", name.status, + KZT_GUEST_FIELD_TRUNCATED); + check_string("name.truncated.value", name.value, "abcd"); + kzt_guest_link_map_string_clear(&name); + + check_int("name.read-error", + kzt_guest_link_map_read_name_snapshot((uintptr_t)(storage + 96), + &ops, + 32, + &name), + 0); + check_int("name.read-error.status", name.status, + KZT_GUEST_FIELD_READ_ERROR); + check_true("name.read-error.value", name.value == NULL); + kzt_guest_link_map_string_clear(&name); + + check_int("name.unknown-null", + kzt_guest_link_map_read_name_snapshot(0, &ops, 32, &name), + 0); + check_int("name.unknown-null.status", name.status, + KZT_GUEST_FIELD_UNKNOWN); + check_true("name.unknown-null.value", name.value == NULL); + kzt_guest_link_map_string_clear(&name); +} + +static void test_allocation_failure_is_read_error_not_borrowed_pointer(void) +{ + char guest_name[] = "/guest/liballoc.so"; + fake_reader_memory_t memory = fake_memory_for(guest_name, + sizeof(guest_name), + NULL, + 0); + kzt_guest_link_map_reader_ops_t ops = fake_ops(&memory); + kzt_guest_string_field_t name = { 0 }; + + kzt_guest_link_map_reader_test_set_alloc_failure_after(0); + check_int("name.alloc-failure", + kzt_guest_link_map_read_name_snapshot((uintptr_t)guest_name, + &ops, + 64, + &name), + 0); + kzt_guest_link_map_reader_test_set_alloc_failure_after(-1); + + check_int("name.alloc-failure.status", name.status, + KZT_GUEST_FIELD_READ_ERROR); + check_true("name.alloc-failure.value", name.value == NULL); +} + +int main(void) +{ + test_valid_link_map_reads_complete_observation(); + test_invalid_link_map_is_identity_failure(); + test_field_read_failure_forms_partial_observation(); + test_name_snapshot_status_matrix(); + test_allocation_failure_is_read_error_not_borrowed_pointer(); + + if (failures) { + fprintf(stderr, "kzt-guest-link-map-reader: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-guest-link-map-reader: all contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_registry.c b/tests/unit/kzt/test_guest_registry.c new file mode 100644 index 0000000000..d435c1c2ad --- /dev/null +++ b/tests/unit/kzt/test_guest_registry.c @@ -0,0 +1,617 @@ +#include +#include + +#include "target/i386/latx/include/kzt_guest_registry.h" + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_not_int(const char *name, int got, int unexpected) +{ + if (got != unexpected) { + return; + } + + fprintf(stderr, "%s: got unexpected %d\n", name, got); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void check_string(const char *name, const char *got, + const char *expected) +{ + if ((!got && !expected) || (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static void check_contains(const char *name, const char *haystack, + const char *needle) +{ + if (haystack && needle && strstr(haystack, needle)) { + return; + } + + fprintf(stderr, "%s: missing \"%s\" in \"%s\"\n", name, + needle ? needle : "(null)", haystack ? haystack : "(null)"); + ++failures; +} + +static kzt_guest_object_observation_t make_observation(uintptr_t link_map_addr) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { 0x100000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x101000, KZT_GUEST_FIELD_OK }, + .map_start = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .path = { "/guest/libfoo.so", KZT_GUEST_FIELD_OK }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static kzt_guest_object_snapshot_t *find_snapshot( + kzt_guest_registry_t *registry, + uintptr_t link_map_addr) +{ + kzt_guest_object_snapshot_t *snapshot = NULL; + + check_int("find_by_link_map", kzt_guest_registry_find_by_link_map( + registry, link_map_addr, &snapshot), 0); + check_true("find_by_link_map.snapshot", snapshot != NULL); + return snapshot; +} + +static void assert_not_found(kzt_guest_registry_t *registry, + uintptr_t link_map_addr) +{ + kzt_guest_object_snapshot_t *snapshot = (void *)0x1; + + check_not_int("find_by_link_map.missing", + kzt_guest_registry_find_by_link_map(registry, + link_map_addr, + &snapshot), + 0); + check_true("find_by_link_map.missing-snapshot", snapshot == NULL); +} + +static void assert_snapshot_identity( + const kzt_guest_object_snapshot_t *snapshot, + uintptr_t link_map_addr, + unsigned long generation) +{ + check_uintptr("snapshot.link_map_addr", snapshot->link_map_addr, + link_map_addr); + check_ulong("snapshot.generation", snapshot->generation, generation); + check_int("snapshot.state", snapshot->state, KZT_GUEST_OBJECT_DISCOVERED); +} + +static void test_first_and_repeat_observation_keep_generation(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x1000); + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + observation.load_bias.value = 0; + observation.path.value = ""; + + check_int("observe.added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + snapshot = find_snapshot(registry, 0x1000); + assert_snapshot_identity(snapshot, 0x1000, 1); + check_int("snapshot.load_bias.status", snapshot->load_bias.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.load_bias.value", snapshot->load_bias.value, 0); + check_int("snapshot.path.status", snapshot->path.status, + KZT_GUEST_FIELD_OK); + check_string("snapshot.path.value", snapshot->path.value, ""); + check_int("snapshot.soname.status", snapshot->soname.status, + KZT_GUEST_FIELD_NOT_PARSED); + check_int("snapshot.dynamic_view_status", snapshot->dynamic_view_status, + KZT_GUEST_FIELD_NOT_PARSED); + check_int("snapshot.map_start.status", snapshot->map_start.status, + KZT_GUEST_FIELD_UNKNOWN); + check_int("snapshot.namespace_id.status", snapshot->namespace_id.status, + KZT_GUEST_FIELD_UNKNOWN); + kzt_guest_object_snapshot_free(snapshot); + + check_int("observe.unchanged", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_UNCHANGED); + + snapshot = find_snapshot(registry, 0x1000); + assert_snapshot_identity(snapshot, 0x1000, 1); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_unknown_fields_are_completed_without_generation_change(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x2000); + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + observation.load_bias.status = KZT_GUEST_FIELD_UNKNOWN; + observation.dynamic_addr.status = KZT_GUEST_FIELD_READ_ERROR; + observation.path.value = NULL; + observation.path.status = KZT_GUEST_FIELD_UNKNOWN; + + check_int("observe.partial-added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + snapshot = find_snapshot(registry, 0x2000); + assert_snapshot_identity(snapshot, 0x2000, 1); + check_int("snapshot.load_bias.unknown", snapshot->load_bias.status, + KZT_GUEST_FIELD_UNKNOWN); + check_int("snapshot.dynamic_addr.read-error", + snapshot->dynamic_addr.status, KZT_GUEST_FIELD_READ_ERROR); + check_int("snapshot.path.unknown", snapshot->path.status, + KZT_GUEST_FIELD_UNKNOWN); + kzt_guest_object_snapshot_free(snapshot); + + observation.load_bias.value = 0x220000; + observation.load_bias.status = KZT_GUEST_FIELD_OK; + observation.dynamic_addr.value = 0x221000; + observation.dynamic_addr.status = KZT_GUEST_FIELD_OK; + observation.path.value = "/guest/libfilled.so"; + observation.path.status = KZT_GUEST_FIELD_OK; + + check_int("observe.updated", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_UPDATED); + + snapshot = find_snapshot(registry, 0x2000); + assert_snapshot_identity(snapshot, 0x2000, 1); + check_int("snapshot.load_bias.ok", snapshot->load_bias.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.load_bias.filled", snapshot->load_bias.value, + 0x220000); + check_int("snapshot.dynamic_addr.ok", snapshot->dynamic_addr.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.dynamic_addr.filled", snapshot->dynamic_addr.value, + 0x221000); + check_int("snapshot.path.ok", snapshot->path.status, KZT_GUEST_FIELD_OK); + check_string("snapshot.path.filled", snapshot->path.value, + "/guest/libfilled.so"); + kzt_guest_object_snapshot_free(snapshot); + + observation.load_bias.status = KZT_GUEST_FIELD_READ_ERROR; + observation.dynamic_addr.status = KZT_GUEST_FIELD_UNKNOWN; + observation.path.value = NULL; + observation.path.status = KZT_GUEST_FIELD_READ_ERROR; + + check_int("observe.failed-fields-unchanged", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_UNCHANGED); + + snapshot = find_snapshot(registry, 0x2000); + assert_snapshot_identity(snapshot, 0x2000, 1); + check_int("snapshot.load_bias.still-ok", snapshot->load_bias.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.load_bias.still-filled", snapshot->load_bias.value, + 0x220000); + check_int("snapshot.dynamic_addr.still-ok", snapshot->dynamic_addr.status, + KZT_GUEST_FIELD_OK); + check_uintptr("snapshot.dynamic_addr.still-filled", + snapshot->dynamic_addr.value, 0x221000); + check_int("snapshot.path.still-ok", snapshot->path.status, + KZT_GUEST_FIELD_OK); + check_string("snapshot.path.still-filled", snapshot->path.value, + "/guest/libfilled.so"); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_identity_conflict_preserves_original_record(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x3000); + kzt_guest_object_snapshot_t *snapshot; + kzt_guest_registry_dump_t dump = { 0 }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + observation.load_bias.value = 0x300000; + observation.dynamic_addr.value = 0x301000; + observation.path.value = "/guest/liboriginal.so"; + + check_int("observe.added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + observation.load_bias.value = 0x310000; + observation.dynamic_addr.value = 0x311000; + observation.path.value = "/guest/libconflict.so"; + + check_int("observe.conflict", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_CONFLICT); + + snapshot = find_snapshot(registry, 0x3000); + assert_snapshot_identity(snapshot, 0x3000, 1); + check_uintptr("snapshot.load_bias.original", snapshot->load_bias.value, + 0x300000); + check_uintptr("snapshot.dynamic_addr.original", + snapshot->dynamic_addr.value, 0x301000); + check_string("snapshot.path.original", snapshot->path.value, + "/guest/liboriginal.so"); + kzt_guest_object_snapshot_free(snapshot); + + check_int("dump.snapshot", + kzt_guest_registry_dump_snapshot(registry, &dump), + 0); + check_ulong("dump.count", dump.count, 1); + assert_snapshot_identity(&dump.objects[0], 0x3000, 1); + check_string("dump.path.original", dump.objects[0].path.value, + "/guest/liboriginal.so"); + kzt_guest_registry_dump_free(&dump); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_partial_observation_and_invalid_identity(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0); + kzt_guest_object_snapshot_t *snapshot; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.invalid-link-map", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ERROR); + assert_not_found(registry, 0); + + observation = make_observation(0x4000); + observation.load_bias.status = KZT_GUEST_FIELD_READ_ERROR; + observation.dynamic_addr.status = KZT_GUEST_FIELD_UNKNOWN; + observation.path.value = "/guest/path-truncated"; + observation.path.status = KZT_GUEST_FIELD_TRUNCATED; + + check_int("observe.partial-added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + snapshot = find_snapshot(registry, 0x4000); + assert_snapshot_identity(snapshot, 0x4000, 1); + check_int("snapshot.load_bias.read-error", snapshot->load_bias.status, + KZT_GUEST_FIELD_READ_ERROR); + check_int("snapshot.dynamic_addr.unknown", snapshot->dynamic_addr.status, + KZT_GUEST_FIELD_UNKNOWN); + check_int("snapshot.path.truncated", snapshot->path.status, + KZT_GUEST_FIELD_TRUNCATED); + check_string("snapshot.path.truncated-value", snapshot->path.value, + "/guest/path-truncated"); + kzt_guest_object_snapshot_free(snapshot); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_query_and_dump_snapshots_are_caller_owned(void) +{ + static const char path_literal[] = "/guest/libowned.so"; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x5000); + kzt_guest_object_snapshot_t *first; + kzt_guest_object_snapshot_t *second; + kzt_guest_registry_dump_t dump = { 0 }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + observation.path.value = path_literal; + check_int("observe.added", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + + first = find_snapshot(registry, 0x5000); + second = find_snapshot(registry, 0x5000); + check_string("first.path", first->path.value, path_literal); + check_string("second.path", second->path.value, path_literal); + check_true("first.path.not-observation", first->path.value != path_literal); + check_true("second.path.not-observation", + second->path.value != path_literal); + check_true("snapshots.path.distinct", + first->path.value != second->path.value); + + check_int("dump.snapshot", + kzt_guest_registry_dump_snapshot(registry, &dump), + 0); + check_ulong("dump.count", dump.count, 1); + check_string("dump.path", dump.objects[0].path.value, path_literal); + check_true("dump.path.not-observation", + dump.objects[0].path.value != path_literal); + check_true("dump.path.not-first", + dump.objects[0].path.value != first->path.value); + check_true("dump.path.not-second", + dump.objects[0].path.value != second->path.value); + + kzt_guest_registry_dump_free(&dump); + kzt_guest_object_snapshot_free(first); + kzt_guest_object_snapshot_free(second); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +typedef struct dump_text_sink { + char text[8192]; + size_t used; + unsigned long calls; + kzt_guest_registry_t *registry; +} dump_text_sink_t; + +static int collect_dump_text_line(const char *line, void *opaque) +{ + dump_text_sink_t *sink = opaque; + kzt_guest_registry_diagnostics_t diagnostics = { 0 }; + size_t len; + size_t remaining; + + if (sink->registry) { + check_int("dump.sink-can-query-registry", + kzt_guest_registry_get_diagnostics(sink->registry, + &diagnostics), + 0); + } + + ++sink->calls; + len = strlen(line); + remaining = sizeof(sink->text) - sink->used; + if (remaining <= 2) { + return 0; + } + if (len >= remaining - 1) { + len = remaining - 2; + } + memcpy(sink->text + sink->used, line, len); + sink->used += len; + sink->text[sink->used++] = '\n'; + sink->text[sink->used] = '\0'; + return 0; +} + +static void test_diagnostics_are_opt_in_and_throttled(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x8000); + kzt_guest_registry_observation_diagnostic_t diagnostic = { 0 }; + kzt_guest_registry_diagnostic_report_t report; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 2, + }; + dump_text_sink_t sink = { 0 }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.diagnostic-disabled", + kzt_guest_registry_observe_with_diagnostic(registry, + &observation, + &diagnostic), + KZT_GUEST_REGISTRY_ADDED); + check_int("diagnostic.default-enabled", diagnostic.enabled, 0); + check_int("diagnostic.default-emitted", diagnostic.emitted, 0); + check_int("diagnostic.default-result", diagnostic.result, + KZT_GUEST_REGISTRY_ADDED); + + check_int("diagnostic.report.disabled", + kzt_guest_registry_get_diagnostic_report(registry, &report), + 0); + check_int("report.default-enabled", report.config.enabled, 0); + check_ulong("report.default-added-events", + report.events[KZT_GUEST_REGISTRY_ADDED].observed, 0); + check_ulong("report.default-added-counter", report.counters.added, 1); + + check_int("diagnostic.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + check_int("observe.unchanged.first", + kzt_guest_registry_observe_with_diagnostic(registry, + &observation, + &diagnostic), + KZT_GUEST_REGISTRY_UNCHANGED); + check_int("diagnostic.first-enabled", diagnostic.enabled, 1); + check_int("diagnostic.first-emitted", diagnostic.emitted, 1); + check_ulong("diagnostic.first-observations", + diagnostic.result_observations, 1); + check_ulong("diagnostic.first-suppressed", diagnostic.result_suppressed, + 0); + + check_int("observe.unchanged.second", + kzt_guest_registry_observe_with_diagnostic(registry, + &observation, + &diagnostic), + KZT_GUEST_REGISTRY_UNCHANGED); + check_int("diagnostic.second-emitted", diagnostic.emitted, 1); + check_ulong("diagnostic.second-observations", + diagnostic.result_observations, 2); + + check_int("observe.unchanged.third", + kzt_guest_registry_observe_with_diagnostic(registry, + &observation, + &diagnostic), + KZT_GUEST_REGISTRY_UNCHANGED); + check_int("diagnostic.third-emitted", diagnostic.emitted, 0); + check_ulong("diagnostic.third-observations", + diagnostic.result_observations, 3); + check_ulong("diagnostic.third-suppressed", diagnostic.result_suppressed, + 1); + + check_int("diagnostic.report.enabled", + kzt_guest_registry_get_diagnostic_report(registry, &report), + 0); + check_int("report.enabled", report.config.enabled, 1); + check_ulong("report.throttle-limit", report.config.throttle_limit, 2); + check_ulong("report.unchanged-observed", + report.events[KZT_GUEST_REGISTRY_UNCHANGED].observed, 3); + check_ulong("report.unchanged-emitted", + report.events[KZT_GUEST_REGISTRY_UNCHANGED].emitted, 2); + check_ulong("report.unchanged-suppressed", + report.events[KZT_GUEST_REGISTRY_UNCHANGED].suppressed, 1); + check_uintptr("report.unchanged-last-link-map", + report.events[KZT_GUEST_REGISTRY_UNCHANGED] + .last_link_map_addr, + 0x8000); + + sink.registry = registry; + check_int("dump.text", + kzt_guest_registry_dump_text(registry, collect_dump_text_line, + &sink), + 0); + check_true("dump.text-called", sink.calls > 0); + check_contains("dump.text-summary", sink.text, + "enabled=1 throttle_limit=2"); + check_contains("dump.text-event", sink.text, + "result=unchanged observed=3 emitted=2 suppressed=1"); + check_contains("dump.text-object", sink.text, + "object link_map=0x8000 generation=1 state=0"); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_init_failure_creates_disabled_registry_diagnostics(void) +{ + kzt_guest_registry_t *registry; + kzt_guest_object_observation_t observation = make_observation(0x6000); + kzt_guest_registry_diagnostics_t diagnostics = { 0 }; + + kzt_guest_registry_test_set_alloc_failure_after(1); + registry = kzt_guest_registry_init(); + kzt_guest_registry_test_set_alloc_failure_after(-1); + + check_true("registry.disabled-init", registry != NULL); + if (!registry) { + return; + } + + check_int("observe.disabled", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_DISABLED); + + check_int("diagnostics.disabled", + kzt_guest_registry_get_diagnostics(registry, &diagnostics), 0); + check_ulong("diagnostics.init-failures", diagnostics.init_failures, 1); + check_ulong("diagnostics.alloc-failures", diagnostics.allocation_failures, + 1); + check_ulong("diagnostics.disabled-observe", diagnostics.disabled, 1); + check_ulong("diagnostics.observations", diagnostics.observations, 1); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); +} + +static void test_destroyed_registry_rejects_new_observation(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t observation = make_observation(0x7000); + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy", registry == NULL); + check_int("observe.after-destroy", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_DISABLED); +} + +int main(void) +{ + test_first_and_repeat_observation_keep_generation(); + test_unknown_fields_are_completed_without_generation_change(); + test_identity_conflict_preserves_original_record(); + test_partial_observation_and_invalid_identity(); + test_query_and_dump_snapshots_are_caller_owned(); + test_diagnostics_are_opt_in_and_throttled(); + test_init_failure_creates_disabled_registry_diagnostics(); + test_destroyed_registry_rejects_new_observation(); + + if (failures) { + fprintf(stderr, "kzt-guest-registry: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-guest-registry: all contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_guest_registry_concurrency.c b/tests/unit/kzt/test_guest_registry_concurrency.c new file mode 100644 index 0000000000..594e443493 --- /dev/null +++ b/tests/unit/kzt/test_guest_registry_concurrency.c @@ -0,0 +1,529 @@ +#include +#include +#include + +#include "target/i386/latx/include/kzt_guest_registry.h" + +#define SAME_LINK_THREADS 16 +#define DIFFERENT_LINK_THREADS 12 +#define SNAPSHOT_THREADS 8 +#define SNAPSHOT_ITERATIONS 64 +#define SNAPSHOT_OBJECTS 6 + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++failures; +} + +static void check_string(const char *name, const char *got, + const char *expected) +{ + if ((!got && !expected) || (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++failures; +} + +static void thread_check_true(int *thread_failures, const char *name, + int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++*thread_failures; +} + +static void thread_check_int(int *thread_failures, const char *name, + int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++*thread_failures; +} + +static void thread_check_ulong(int *thread_failures, const char *name, + unsigned long got, unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, got, expected); + ++*thread_failures; +} + +static void thread_check_string(int *thread_failures, const char *name, + const char *got, const char *expected) +{ + if ((!got && !expected) || (got && expected && !strcmp(got, expected))) { + return; + } + + fprintf(stderr, "%s: got \"%s\" expected \"%s\"\n", name, + got ? got : "(null)", expected ? expected : "(null)"); + ++*thread_failures; +} + +static kzt_guest_object_observation_t make_observation( + uintptr_t link_map_addr, + uintptr_t index, + const char *path) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { 0x100000 + index * 0x1000, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { 0x101000 + index * 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .map_end = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .namespace_id = { 0, KZT_GUEST_FIELD_UNKNOWN }, + .path = { path, KZT_GUEST_FIELD_OK }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static int wait_for_barrier(pthread_barrier_t *barrier) +{ + int ret = pthread_barrier_wait(barrier); + + return ret == 0 || ret == PTHREAD_BARRIER_SERIAL_THREAD ? 0 : ret; +} + +typedef struct observe_worker { + kzt_guest_registry_t *registry; + pthread_barrier_t *barrier; + kzt_guest_object_observation_t observation; + kzt_guest_registry_result_t result; + int failures; +} observe_worker_t; + +static void *observe_worker_main(void *opaque) +{ + observe_worker_t *worker = opaque; + + if (wait_for_barrier(worker->barrier) != 0) { + worker->result = KZT_GUEST_REGISTRY_ERROR; + ++worker->failures; + return NULL; + } + + worker->result = kzt_guest_registry_observe(worker->registry, + &worker->observation); + return NULL; +} + +static void join_observe_workers(pthread_t *threads, + observe_worker_t *workers, + size_t count) +{ + size_t i; + + for (i = 0; i < count; ++i) { + check_int("pthread.join", pthread_join(threads[i], NULL), 0); + failures += workers[i].failures; + } +} + +static const kzt_guest_object_snapshot_t *find_dump_object( + const kzt_guest_registry_dump_t *dump, + uintptr_t link_map_addr) +{ + size_t i; + + for (i = 0; i < dump->count; ++i) { + if (dump->objects[i].link_map_addr == link_map_addr) { + return &dump->objects[i]; + } + } + + return NULL; +} + +static void test_concurrent_same_link_map_converges_to_one_generation(void) +{ + static const uintptr_t link_map_addr = 0x710000; + static const char path[] = "/guest/libsame-concurrent.so"; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + pthread_barrier_t barrier; + pthread_t threads[SAME_LINK_THREADS]; + observe_worker_t workers[SAME_LINK_THREADS]; + kzt_guest_object_snapshot_t *snapshot = NULL; + kzt_guest_registry_dump_t dump = { 0 }; + kzt_guest_registry_diagnostics_t diagnostics = { 0 }; + unsigned long added = 0; + unsigned long unchanged = 0; + size_t i; + + check_true("registry.init.same", registry != NULL); + if (!registry) { + return; + } + + check_int("barrier.init.same", + pthread_barrier_init(&barrier, NULL, SAME_LINK_THREADS), 0); + + for (i = 0; i < SAME_LINK_THREADS; ++i) { + workers[i] = (observe_worker_t) { + .registry = registry, + .barrier = &barrier, + .observation = make_observation(link_map_addr, 0, path), + .result = KZT_GUEST_REGISTRY_ERROR, + }; + check_int("pthread.create.same", + pthread_create(&threads[i], NULL, observe_worker_main, + &workers[i]), 0); + } + + join_observe_workers(threads, workers, SAME_LINK_THREADS); + check_int("barrier.destroy.same", pthread_barrier_destroy(&barrier), 0); + + for (i = 0; i < SAME_LINK_THREADS; ++i) { + if (workers[i].result == KZT_GUEST_REGISTRY_ADDED) { + ++added; + } else if (workers[i].result == KZT_GUEST_REGISTRY_UNCHANGED) { + ++unchanged; + } else { + fprintf(stderr, "same-link worker %lu unexpected result %d\n", + (unsigned long)i, workers[i].result); + ++failures; + } + } + + check_ulong("same.added", added, 1); + check_ulong("same.unchanged", unchanged, SAME_LINK_THREADS - 1); + + check_int("same.find", + kzt_guest_registry_find_by_link_map(registry, link_map_addr, + &snapshot), 0); + check_true("same.snapshot", snapshot != NULL); + if (snapshot) { + check_ulong("same.snapshot.generation", snapshot->generation, 1); + check_string("same.snapshot.path", snapshot->path.value, path); + kzt_guest_object_snapshot_free(snapshot); + } + + check_int("same.dump", kzt_guest_registry_dump_snapshot(registry, &dump), + 0); + check_ulong("same.dump.count", dump.count, 1); + if (dump.count == 1) { + check_ulong("same.dump.generation", dump.objects[0].generation, 1); + check_string("same.dump.path", dump.objects[0].path.value, path); + } + kzt_guest_registry_dump_free(&dump); + + check_int("same.diagnostics", + kzt_guest_registry_get_diagnostics(registry, &diagnostics), 0); + check_ulong("same.diagnostics.observations", diagnostics.observations, + SAME_LINK_THREADS); + check_ulong("same.diagnostics.added", diagnostics.added, 1); + check_ulong("same.diagnostics.unchanged", diagnostics.unchanged, + SAME_LINK_THREADS - 1); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy.same", registry == NULL); +} + +static void test_concurrent_different_link_maps_create_distinct_objects(void) +{ + static const uintptr_t base_link_map_addr = 0x720000; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + pthread_barrier_t barrier; + pthread_t threads[DIFFERENT_LINK_THREADS]; + observe_worker_t workers[DIFFERENT_LINK_THREADS]; + char paths[DIFFERENT_LINK_THREADS][64]; + unsigned char seen_generation[DIFFERENT_LINK_THREADS + 1] = { 0 }; + kzt_guest_registry_dump_t dump = { 0 }; + size_t i; + + check_true("registry.init.different", registry != NULL); + if (!registry) { + return; + } + + check_int("barrier.init.different", + pthread_barrier_init(&barrier, NULL, DIFFERENT_LINK_THREADS), 0); + + for (i = 0; i < DIFFERENT_LINK_THREADS; ++i) { + uintptr_t link_map_addr = base_link_map_addr + i * 0x1000; + + snprintf(paths[i], sizeof(paths[i]), + "/guest/libdifferent-%02lu.so", (unsigned long)i); + workers[i] = (observe_worker_t) { + .registry = registry, + .barrier = &barrier, + .observation = make_observation(link_map_addr, i + 1, paths[i]), + .result = KZT_GUEST_REGISTRY_ERROR, + }; + check_int("pthread.create.different", + pthread_create(&threads[i], NULL, observe_worker_main, + &workers[i]), 0); + } + + join_observe_workers(threads, workers, DIFFERENT_LINK_THREADS); + check_int("barrier.destroy.different", + pthread_barrier_destroy(&barrier), 0); + + for (i = 0; i < DIFFERENT_LINK_THREADS; ++i) { + kzt_guest_object_snapshot_t *snapshot = NULL; + unsigned long generation; + + check_int("different.result", workers[i].result, + KZT_GUEST_REGISTRY_ADDED); + check_int("different.find", + kzt_guest_registry_find_by_link_map( + registry, workers[i].observation.link_map_addr, + &snapshot), + 0); + check_true("different.snapshot", snapshot != NULL); + if (!snapshot) { + continue; + } + + check_string("different.snapshot.path", snapshot->path.value, + paths[i]); + generation = snapshot->generation; + check_true("different.generation.range", + generation >= 1 && generation <= DIFFERENT_LINK_THREADS); + if (generation >= 1 && generation <= DIFFERENT_LINK_THREADS) { + check_true("different.generation.unique", + !seen_generation[generation]); + seen_generation[generation] = 1; + } + kzt_guest_object_snapshot_free(snapshot); + } + + for (i = 1; i <= DIFFERENT_LINK_THREADS; ++i) { + check_true("different.generation.seen", seen_generation[i]); + } + + check_int("different.dump", + kzt_guest_registry_dump_snapshot(registry, &dump), 0); + check_ulong("different.dump.count", dump.count, DIFFERENT_LINK_THREADS); + for (i = 0; i < DIFFERENT_LINK_THREADS; ++i) { + const kzt_guest_object_snapshot_t *object = find_dump_object( + &dump, base_link_map_addr + i * 0x1000); + + check_true("different.dump.object", object != NULL); + if (object) { + check_string("different.dump.path", object->path.value, paths[i]); + } + } + kzt_guest_registry_dump_free(&dump); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy.different", registry == NULL); +} + +typedef struct snapshot_worker { + kzt_guest_registry_t *registry; + pthread_barrier_t *barrier; + uintptr_t link_map_addr; + const char *expected_path; + const char *source_path; + int failures; +} snapshot_worker_t; + +static void *snapshot_worker_main(void *opaque) +{ + snapshot_worker_t *worker = opaque; + size_t i; + + if (wait_for_barrier(worker->barrier) != 0) { + ++worker->failures; + return NULL; + } + + for (i = 0; i < SNAPSHOT_ITERATIONS; ++i) { + kzt_guest_object_snapshot_t *first = NULL; + kzt_guest_object_snapshot_t *second = NULL; + kzt_guest_registry_dump_t first_dump = { 0 }; + kzt_guest_registry_dump_t second_dump = { 0 }; + const kzt_guest_object_snapshot_t *first_object; + const kzt_guest_object_snapshot_t *second_object; + + thread_check_int( + &worker->failures, "snapshot.find.first", + kzt_guest_registry_find_by_link_map(worker->registry, + worker->link_map_addr, + &first), + 0); + thread_check_int( + &worker->failures, "snapshot.find.second", + kzt_guest_registry_find_by_link_map(worker->registry, + worker->link_map_addr, + &second), + 0); + thread_check_true(&worker->failures, "snapshot.find.first.ptr", + first != NULL); + thread_check_true(&worker->failures, "snapshot.find.second.ptr", + second != NULL); + if (first && second) { + thread_check_string(&worker->failures, "snapshot.find.path.first", + first->path.value, worker->expected_path); + thread_check_string(&worker->failures, "snapshot.find.path.second", + second->path.value, worker->expected_path); + thread_check_true(&worker->failures, + "snapshot.find.path.not-source", + first->path.value != worker->source_path); + thread_check_true(&worker->failures, + "snapshot.find.path.distinct", + first->path.value != second->path.value); + } + + thread_check_int(&worker->failures, "snapshot.dump.first", + kzt_guest_registry_dump_snapshot(worker->registry, + &first_dump), + 0); + thread_check_int(&worker->failures, "snapshot.dump.second", + kzt_guest_registry_dump_snapshot(worker->registry, + &second_dump), + 0); + thread_check_ulong(&worker->failures, "snapshot.dump.first.count", + first_dump.count, SNAPSHOT_OBJECTS); + thread_check_ulong(&worker->failures, "snapshot.dump.second.count", + second_dump.count, SNAPSHOT_OBJECTS); + + first_object = find_dump_object(&first_dump, worker->link_map_addr); + second_object = find_dump_object(&second_dump, worker->link_map_addr); + thread_check_true(&worker->failures, "snapshot.dump.first.object", + first_object != NULL); + thread_check_true(&worker->failures, "snapshot.dump.second.object", + second_object != NULL); + if (first_object && second_object) { + thread_check_string(&worker->failures, "snapshot.dump.path.first", + first_object->path.value, + worker->expected_path); + thread_check_string(&worker->failures, "snapshot.dump.path.second", + second_object->path.value, + worker->expected_path); + thread_check_true(&worker->failures, + "snapshot.dump.path.not-source", + first_object->path.value != worker->source_path); + thread_check_true(&worker->failures, + "snapshot.dump.path.distinct", + first_object->path.value != + second_object->path.value); + if (first) { + thread_check_true(&worker->failures, + "snapshot.dump.path.not-find", + first_object->path.value != + first->path.value); + } + } + + kzt_guest_registry_dump_free(&second_dump); + kzt_guest_registry_dump_free(&first_dump); + kzt_guest_object_snapshot_free(second); + kzt_guest_object_snapshot_free(first); + } + + return NULL; +} + +static void test_concurrent_query_and_dump_return_owned_snapshots(void) +{ + static const uintptr_t base_link_map_addr = 0x730000; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + pthread_barrier_t barrier; + pthread_t threads[SNAPSHOT_THREADS]; + snapshot_worker_t workers[SNAPSHOT_THREADS]; + char paths[SNAPSHOT_OBJECTS][64]; + size_t i; + + check_true("registry.init.snapshot", registry != NULL); + if (!registry) { + return; + } + + for (i = 0; i < SNAPSHOT_OBJECTS; ++i) { + kzt_guest_object_observation_t observation; + + snprintf(paths[i], sizeof(paths[i]), "/guest/libsnapshot-%02lu.so", + (unsigned long)i); + observation = make_observation(base_link_map_addr + i * 0x1000, + i + 1, paths[i]); + check_int("snapshot.prepopulate", + kzt_guest_registry_observe(registry, &observation), + KZT_GUEST_REGISTRY_ADDED); + } + + check_int("barrier.init.snapshot", + pthread_barrier_init(&barrier, NULL, SNAPSHOT_THREADS), 0); + + for (i = 0; i < SNAPSHOT_THREADS; ++i) { + size_t object_index = i % SNAPSHOT_OBJECTS; + + workers[i] = (snapshot_worker_t) { + .registry = registry, + .barrier = &barrier, + .link_map_addr = base_link_map_addr + object_index * 0x1000, + .expected_path = paths[object_index], + .source_path = paths[object_index], + }; + check_int("pthread.create.snapshot", + pthread_create(&threads[i], NULL, snapshot_worker_main, + &workers[i]), 0); + } + + for (i = 0; i < SNAPSHOT_THREADS; ++i) { + check_int("pthread.join.snapshot", pthread_join(threads[i], NULL), 0); + failures += workers[i].failures; + } + check_int("barrier.destroy.snapshot", pthread_barrier_destroy(&barrier), + 0); + + kzt_guest_registry_destroy(®istry); + check_true("registry.destroy.snapshot", registry == NULL); +} + +int main(void) +{ + test_concurrent_same_link_map_converges_to_one_generation(); + test_concurrent_different_link_maps_create_distinct_objects(); + test_concurrent_query_and_dump_return_owned_snapshots(); + + if (failures) { + fprintf(stderr, "kzt-guest-registry-concurrency: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-guest-registry-concurrency: all tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_observation_adapter.c b/tests/unit/kzt/test_observation_adapter.c new file mode 100644 index 0000000000..560a8001e7 --- /dev/null +++ b/tests/unit/kzt/test_observation_adapter.c @@ -0,0 +1,888 @@ +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/kzt_observation_adapter.h" + +#define EVENT_READER_READ 1 +#define EVENT_LEGACY_FLOW 2 +#define EVENT_DIAGNOSTIC 3 + +#define TEST_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +typedef struct fake_read_failure { + uintptr_t addr; + size_t size; +} fake_read_failure_t; + +typedef struct fake_reader_memory { + uintptr_t base; + size_t size; + const fake_read_failure_t *failures; + size_t failure_count; +} fake_reader_memory_t; + +typedef struct observation_trace { + int events[128]; + size_t event_count; + int reader_calls; + int legacy_calls; + int diagnostic_calls; + int legacy_return; + uintptr_t legacy_link_map_addr; + kzt_observation_adapter_result_t diagnostic_result; + int diagnostic_emitted; + unsigned long diagnostic_result_observations; + int dynamic_attempted; + int dynamic_parse_return; + uintptr_t dynamic_addr; + kzt_guest_dynamic_status_t dynamic_status; + kzt_guest_dynamic_error_t dynamic_error; + size_t dynamic_entry_count; + uintptr_t dynamic_read_error_addr; + int dynamic_commit_attempted; + kzt_guest_registry_result_t dynamic_commit_result; + int dynamic_registry_emitted; +} observation_trace_t; + +typedef struct fake_callback_event { + struct link_map_x64 link_map; + char guest_name[64]; + Elf64_Dyn dynamic[4]; + fake_reader_memory_t memory; + kzt_guest_link_map_reader_ops_t ops; + observation_trace_t trace; +} fake_callback_event_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static void check_ulong(const char *name, unsigned long got, + unsigned long expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %lu expected %lu\n", name, got, expected); + ++failures; +} + +static void check_uintptr(const char *name, uintptr_t got, + uintptr_t expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got 0x%lx expected 0x%lx\n", name, + (unsigned long)got, (unsigned long)expected); + ++failures; +} + +static void record_event(observation_trace_t *trace, int event) +{ + if (trace->event_count < TEST_ARRAY_SIZE(trace->events)) { + trace->events[trace->event_count++] = event; + } +} + +static int ranges_overlap(uintptr_t left_addr, size_t left_size, + uintptr_t right_addr, size_t right_size) +{ + uintptr_t left_end = left_addr + left_size; + uintptr_t right_end = right_addr + right_size; + + return left_addr < right_end && right_addr < left_end; +} + +static int fake_read_memory(uintptr_t guest_addr, void *dst, size_t size, + void *opaque) +{ + fake_callback_event_t *event = opaque; + fake_reader_memory_t *memory = &event->memory; + size_t i; + + ++event->trace.reader_calls; + record_event(&event->trace, EVENT_READER_READ); + + for (i = 0; i < memory->failure_count; ++i) { + if (ranges_overlap(guest_addr, size, + memory->failures[i].addr, + memory->failures[i].size)) { + return -1; + } + } + + if (guest_addr < memory->base || + size > memory->size || + guest_addr - memory->base > memory->size - size) { + return -1; + } + + memcpy(dst, (const void *)guest_addr, size); + return 0; +} + +static int fake_legacy_flow(uintptr_t link_map_addr, void *opaque) +{ + observation_trace_t *trace = opaque; + + ++trace->legacy_calls; + trace->legacy_link_map_addr = link_map_addr; + record_event(trace, EVENT_LEGACY_FLOW); + return trace->legacy_return; +} + +static void fake_diagnostic( + const kzt_observation_adapter_diagnostic_t *diagnostic, + void *opaque) +{ + observation_trace_t *trace = opaque; + + ++trace->diagnostic_calls; + trace->diagnostic_result = diagnostic->result; + trace->diagnostic_emitted = diagnostic->emitted; + trace->diagnostic_result_observations = + diagnostic->registry.result_observations; + trace->dynamic_attempted = diagnostic->dynamic.attempted; + trace->dynamic_parse_return = diagnostic->dynamic.parse_return; + trace->dynamic_addr = diagnostic->dynamic.dynamic_addr; + trace->dynamic_status = diagnostic->dynamic.status; + trace->dynamic_error = diagnostic->dynamic.error; + trace->dynamic_entry_count = diagnostic->dynamic.entry_count; + trace->dynamic_read_error_addr = diagnostic->dynamic.read_error_addr; + trace->dynamic_commit_attempted = diagnostic->dynamic.commit_attempted; + trace->dynamic_commit_result = diagnostic->dynamic.commit_result; + trace->dynamic_registry_emitted = diagnostic->dynamic.registry.emitted; + record_event(trace, EVENT_DIAGNOSTIC); +} + +static void init_fake_callback_event(fake_callback_event_t *event, + const char *path, + uintptr_t load_bias) +{ + memset(event, 0, sizeof(*event)); + strcpy(event->guest_name, path); + event->dynamic[0].d_tag = DT_SYMTAB; + event->dynamic[0].d_un.d_ptr = load_bias + 0x3000; + event->dynamic[1].d_tag = DT_STRTAB; + event->dynamic[1].d_un.d_ptr = load_bias + 0x4000; + event->dynamic[2].d_tag = DT_STRSZ; + event->dynamic[2].d_un.d_val = 0x180; + event->dynamic[3].d_tag = DT_NULL; + event->dynamic[3].d_un.d_val = 0; + event->link_map.l_addr = load_bias; + event->link_map.l_name = event->guest_name; + event->link_map.l_ld = event->dynamic; + event->link_map.l_ns = 7; + event->link_map.l_map_start = load_bias; + event->link_map.l_map_end = load_bias + 0x20000; + event->memory.base = (uintptr_t)&event->link_map; + event->memory.size = sizeof(*event) - + offsetof(fake_callback_event_t, link_map); + event->ops.read_memory = fake_read_memory; + event->ops.opaque = event; + event->trace.legacy_return = 77; +} + +static kzt_guest_object_observation_t make_observation(uintptr_t link_map_addr, + uintptr_t load_bias, + const char *path) +{ + return (kzt_guest_object_observation_t) { + .link_map_addr = link_map_addr, + .load_bias = { load_bias, KZT_GUEST_FIELD_OK }, + .dynamic_addr = { load_bias + 0x1000, KZT_GUEST_FIELD_OK }, + .map_start = { load_bias, KZT_GUEST_FIELD_OK }, + .map_end = { load_bias + 0x20000, KZT_GUEST_FIELD_OK }, + .namespace_id = { 7, KZT_GUEST_FIELD_OK }, + .path = { path, KZT_GUEST_FIELD_OK }, + .soname = { NULL, KZT_GUEST_FIELD_NOT_PARSED }, + .dynamic_view_status = KZT_GUEST_FIELD_NOT_PARSED, + }; +} + +static void assert_old_flow_exactly_once(const char *name, + const fake_callback_event_t *event, + int expected_return) +{ + check_int(name, event->trace.legacy_calls, 1); + check_int("old-flow.return", event->trace.legacy_return, expected_return); + check_true("old-flow.link-map", + event->trace.legacy_link_map_addr == + (uintptr_t)&event->link_map); +} + +static void assert_reader_before_old_flow(const fake_callback_event_t *event) +{ + size_t i; + size_t first_reader = TEST_ARRAY_SIZE(event->trace.events); + size_t legacy = TEST_ARRAY_SIZE(event->trace.events); + + for (i = 0; i < event->trace.event_count; ++i) { + if (event->trace.events[i] == EVENT_READER_READ && + first_reader == TEST_ARRAY_SIZE(event->trace.events)) { + first_reader = i; + } + if (event->trace.events[i] == EVENT_LEGACY_FLOW && + legacy == TEST_ARRAY_SIZE(event->trace.events)) { + legacy = i; + } + } + + check_true("adapter.reader-ran", + first_reader != TEST_ARRAY_SIZE(event->trace.events)); + check_true("adapter.before-old-flow", first_reader < legacy); +} + +static int run_adapter(fake_callback_event_t *event, + kzt_guest_registry_t *registry, + int enabled, + kzt_observation_adapter_result_t *result) +{ + kzt_observation_adapter_request_t request = { + .enabled = enabled, + .link_map_addr = (uintptr_t)&event->link_map, + .registry = registry, + .reader_ops = &event->ops, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = &event->trace, + }; + + return kzt_observe_guest_object_from_callback(&request, result); +} + +static int run_adapter_with_diagnostics( + fake_callback_event_t *event, + kzt_guest_registry_t *registry, + int enabled, + kzt_observation_adapter_result_t *result) +{ + kzt_observation_adapter_request_t request = { + .enabled = enabled, + .diagnostics_enabled = 1, + .link_map_addr = (uintptr_t)&event->link_map, + .registry = registry, + .reader_ops = &event->ops, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = &event->trace, + .diagnostic = fake_diagnostic, + .diagnostic_opaque = &event->trace, + }; + + return kzt_observe_guest_object_from_callback(&request, result); +} + +static unsigned long registry_object_count(kzt_guest_registry_t *registry) +{ + kzt_guest_registry_dump_t dump = { 0 }; + unsigned long count = 0; + + if (kzt_guest_registry_dump_snapshot(registry, &dump) == 0) { + count = dump.count; + } + kzt_guest_registry_dump_free(&dump); + return count; +} + +static void assert_dynamic_view_complete(const char *name, + kzt_guest_registry_t *registry, + const fake_callback_event_t *event, + unsigned long expected_generation) +{ + kzt_guest_dynamic_view_t view = { 0 }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long generation = 0; + + check_int(name, kzt_guest_registry_find_dynamic_view( + registry, (uintptr_t)&event->link_map, &view, &status, + &generation), 0); + check_int("dynamic.status", status, KZT_GUEST_FIELD_OK); + check_ulong("dynamic.generation", generation, expected_generation); + check_uintptr("dynamic.addr", view.dynamic_addr, + (uintptr_t)event->dynamic); + check_uintptr("dynamic.load-bias", view.load_bias, + event->link_map.l_addr); + check_int("dynamic.view-status", view.status, + KZT_GUEST_DYNAMIC_COMPLETE); + check_ulong("dynamic.entry-count", view.entry_count, 3); + check_int("dynamic.has-null", view.has_null, 1); + check_true("dynamic.symtab.present", view.symtab.present); + check_uintptr("dynamic.symtab", view.symtab.value, + event->link_map.l_addr + 0x3000); + check_int("dynamic.symtab.semantics", view.symtab.address_semantics, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_true("dynamic.strtab.present", view.strtab.present); + check_uintptr("dynamic.strtab", view.strtab.value, + event->link_map.l_addr + 0x4000); + check_int("dynamic.strtab.semantics", view.strtab.address_semantics, + KZT_GUEST_DYNAMIC_RUNTIME_ADDRESS); + check_true("dynamic.strsz.present", view.strsz.present); + check_ulong("dynamic.strsz", view.strsz.value, 0x180); + check_int("dynamic.strsz.semantics", view.strsz.address_semantics, + KZT_GUEST_DYNAMIC_SCALAR); +} + +static void assert_dynamic_view_read_error(const char *name, + kzt_guest_registry_t *registry, + const fake_callback_event_t *event) +{ + kzt_guest_dynamic_view_t view = { 0 }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long generation = 0; + + check_int(name, kzt_guest_registry_find_dynamic_view( + registry, (uintptr_t)&event->link_map, &view, &status, + &generation), 0); + check_int("dynamic.read-error.status", status, + KZT_GUEST_FIELD_READ_ERROR); + check_ulong("dynamic.read-error.generation", generation, 1); + check_uintptr("dynamic.read-error.addr", view.dynamic_addr, + (uintptr_t)event->dynamic); + check_int("dynamic.read-error.view-status", view.status, + KZT_GUEST_DYNAMIC_READ_ERROR); + check_ulong("dynamic.read-error.entry-count", view.entry_count, 1); + check_int("dynamic.read-error.no-null", view.has_null, 0); +} + +static void assert_dynamic_view_not_parsed(const char *name, + kzt_guest_registry_t *registry, + const fake_callback_event_t *event) +{ + kzt_guest_dynamic_view_t view = { 0 }; + kzt_guest_field_status_t status = KZT_GUEST_FIELD_UNKNOWN; + unsigned long generation = 0; + + check_int(name, kzt_guest_registry_find_dynamic_view( + registry, (uintptr_t)&event->link_map, &view, &status, + &generation), 0); + check_int("dynamic.not-parsed.status", status, + KZT_GUEST_FIELD_NOT_PARSED); + check_ulong("dynamic.not-parsed.generation", generation, 1); + check_uintptr("dynamic.not-parsed.addr", view.dynamic_addr, 0); +} + +static void test_active_observation_adds_object_and_preserves_old_flow(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libactive.so", 0x100000); + event.trace.legacy_return = 23; + + ret = run_adapter(&event, registry, 1, &result); + + check_int("active.return", ret, 23); + check_int("active.result", result, KZT_OBSERVATION_ADAPTER_ADDED); + assert_old_flow_exactly_once("active.old-flow", &event, 23); + assert_reader_before_old_flow(&event); + check_ulong("active.registry-count", registry_object_count(registry), 1); + + kzt_guest_registry_destroy(®istry); +} + +static void test_dynamic_parser_success_commits_snapshot(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdynamic.so", 0x180000); + event.trace.legacy_return = 41; + check_int("dynamic-success.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + ret = run_adapter_with_diagnostics(&event, registry, 1, &result); + + check_int("dynamic-success.return", ret, 41); + check_int("dynamic-success.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + assert_old_flow_exactly_once("dynamic-success.old-flow", &event, 41); + assert_dynamic_view_complete("dynamic-success.view", registry, &event, 1); + check_int("dynamic-success.diagnostic-attempted", + event.trace.dynamic_attempted, 1); + check_int("dynamic-success.diagnostic-parse-return", + event.trace.dynamic_parse_return, 0); + check_int("dynamic-success.diagnostic-status", + event.trace.dynamic_status, KZT_GUEST_DYNAMIC_COMPLETE); + check_int("dynamic-success.diagnostic-commit", + event.trace.dynamic_commit_attempted, 1); + check_int("dynamic-success.diagnostic-commit-result", + event.trace.dynamic_commit_result, KZT_GUEST_REGISTRY_UPDATED); + + kzt_guest_registry_destroy(®istry); +} + +static void test_dynamic_parser_read_failure_is_fail_open(void) +{ + fake_callback_event_t event; + fake_read_failure_t failure; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdynamic-readfail.so", + 0x190000); + event.trace.legacy_return = 42; + failure.addr = (uintptr_t)&event.dynamic[1]; + failure.size = sizeof(event.dynamic[1]); + event.memory.failures = &failure; + event.memory.failure_count = 1; + check_int("dynamic-readfail.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + ret = run_adapter_with_diagnostics(&event, registry, 1, &result); + + check_int("dynamic-readfail.return", ret, 42); + check_int("dynamic-readfail.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + assert_old_flow_exactly_once("dynamic-readfail.old-flow", &event, 42); + assert_dynamic_view_read_error("dynamic-readfail.view", registry, &event); + check_int("dynamic-readfail.diagnostic-attempted", + event.trace.dynamic_attempted, 1); + check_int("dynamic-readfail.diagnostic-status", + event.trace.dynamic_status, KZT_GUEST_DYNAMIC_READ_ERROR); + check_uintptr("dynamic-readfail.diagnostic-read-addr", + event.trace.dynamic_read_error_addr, + (uintptr_t)&event.dynamic[1]); + check_int("dynamic-readfail.diagnostic-commit-result", + event.trace.dynamic_commit_result, KZT_GUEST_REGISTRY_UPDATED); + + kzt_guest_registry_destroy(®istry); +} + +static void test_dynamic_commit_failure_is_fail_open(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + kzt_guest_registry_diagnostics_t diagnostics = { 0 }; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdynamic-commitfail.so", + 0x1a0000); + event.trace.legacy_return = 43; + check_int("dynamic-commitfail.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + kzt_guest_registry_test_set_dynamic_commit_failure_after(0); + ret = run_adapter_with_diagnostics(&event, registry, 1, &result); + kzt_guest_registry_test_set_dynamic_commit_failure_after(-1); + + check_int("dynamic-commitfail.return", ret, 43); + check_int("dynamic-commitfail.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + assert_old_flow_exactly_once("dynamic-commitfail.old-flow", &event, 43); + assert_dynamic_view_not_parsed("dynamic-commitfail.view", registry, + &event); + check_int("dynamic-commitfail.diagnostic-attempted", + event.trace.dynamic_attempted, 1); + check_int("dynamic-commitfail.diagnostic-status", + event.trace.dynamic_status, KZT_GUEST_DYNAMIC_COMPLETE); + check_int("dynamic-commitfail.diagnostic-commit-result", + event.trace.dynamic_commit_result, KZT_GUEST_REGISTRY_ERROR); + check_int("dynamic-commitfail.diagnostics", + kzt_guest_registry_get_diagnostics(registry, &diagnostics), 0); + check_ulong("dynamic-commitfail.error-count", diagnostics.errors, 1); + + kzt_guest_registry_destroy(®istry); +} + +static void test_disabled_adapter_skips_observation_but_preserves_old_flow(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdisabled.so", 0x200000); + event.trace.legacy_return = 24; + + ret = run_adapter(&event, registry, 0, &result); + + check_int("disabled.return", ret, 24); + check_int("disabled.result", result, KZT_OBSERVATION_ADAPTER_DISABLED); + assert_old_flow_exactly_once("disabled.old-flow", &event, 24); + check_int("disabled.reader-calls", event.trace.reader_calls, 0); + check_ulong("disabled.registry-count", registry_object_count(registry), 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_reader_failure_is_fail_open_for_old_flow(void) +{ + fake_callback_event_t event; + fake_read_failure_t failure; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libreaderfail.so", 0x300000); + event.trace.legacy_return = 25; + failure.addr = (uintptr_t)&event.link_map + + offsetof(struct link_map_x64, l_addr); + failure.size = sizeof(event.link_map.l_addr); + event.memory.failures = &failure; + event.memory.failure_count = 1; + + ret = run_adapter(&event, registry, 1, &result); + + check_int("reader-failure.return", ret, 25); + check_int("reader-failure.result", result, + KZT_OBSERVATION_ADAPTER_READER_FAILED); + assert_old_flow_exactly_once("reader-failure.old-flow", &event, 25); + assert_reader_before_old_flow(&event); + check_ulong("reader-failure.registry-count", + registry_object_count(registry), 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_registry_failure_is_fail_open_for_old_flow(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libregistryfail.so", 0x400000); + event.trace.legacy_return = 26; + + kzt_guest_registry_test_set_alloc_failure_after(0); + ret = run_adapter(&event, registry, 1, &result); + kzt_guest_registry_test_set_alloc_failure_after(-1); + + check_int("registry-failure.return", ret, 26); + check_int("registry-failure.result", result, + KZT_OBSERVATION_ADAPTER_REGISTRY_FAILED); + assert_old_flow_exactly_once("registry-failure.old-flow", &event, 26); + assert_reader_before_old_flow(&event); + check_ulong("registry-failure.registry-count", + registry_object_count(registry), 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_conflict_result_does_not_change_old_flow(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_guest_object_observation_t original; + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 4, + }; + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libconflict-new.so", 0x500000); + event.trace.legacy_return = 27; + original = make_observation((uintptr_t)&event.link_map, + 0x510000, + "/guest/libconflict-old.so"); + check_int("conflict.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + check_int("conflict.prepopulate", + kzt_guest_registry_observe(registry, &original), + KZT_GUEST_REGISTRY_ADDED); + + ret = run_adapter_with_diagnostics(&event, registry, 1, &result); + + check_int("conflict.return", ret, 27); + check_int("conflict.result", result, KZT_OBSERVATION_ADAPTER_CONFLICT); + assert_old_flow_exactly_once("conflict.old-flow", &event, 27); + assert_reader_before_old_flow(&event); + check_ulong("conflict.registry-count", + registry_object_count(registry), 1); + assert_dynamic_view_not_parsed("conflict.view", registry, &event); + check_int("conflict.diagnostic-calls", event.trace.diagnostic_calls, 1); + check_int("conflict.dynamic-not-attempted", + event.trace.dynamic_attempted, 0); + check_int("conflict.dynamic-no-commit", + event.trace.dynamic_commit_attempted, 0); + check_int("conflict.dynamic-commit-result", + event.trace.dynamic_commit_result, KZT_GUEST_REGISTRY_RESULT_COUNT); + + kzt_guest_registry_destroy(®istry); +} + +static void test_no_callback_event_does_not_create_registry_objects(void) +{ + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + check_ulong("no-callback.registry-count", + registry_object_count(registry), 0); + + kzt_guest_registry_destroy(®istry); +} + +static void test_enabled_diagnostics_are_throttled_and_fail_open(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 1, + }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdiagnostic.so", 0x700000); + event.trace.legacy_return = 31; + check_int("diagnostic.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + check_int("diagnostic.added.return", + run_adapter_with_diagnostics(&event, registry, 1, &result), + 31); + check_int("diagnostic.added.result", result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostic.added.calls", event.trace.diagnostic_calls, 1); + check_int("diagnostic.added.callback-result", + event.trace.diagnostic_result, KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostic.added.emitted", event.trace.diagnostic_emitted, 1); + check_int("diagnostic.added.legacy-calls", event.trace.legacy_calls, 1); + + check_int("diagnostic.unchanged.return", + run_adapter_with_diagnostics(&event, registry, 1, &result), + 31); + check_int("diagnostic.unchanged.result", result, + KZT_OBSERVATION_ADAPTER_UNCHANGED); + check_int("diagnostic.unchanged.calls", event.trace.diagnostic_calls, 2); + check_int("diagnostic.unchanged.callback-result", + event.trace.diagnostic_result, + KZT_OBSERVATION_ADAPTER_UNCHANGED); + check_ulong("diagnostic.unchanged.observations", + event.trace.diagnostic_result_observations, 1); + check_int("diagnostic.unchanged.legacy-calls", + event.trace.legacy_calls, 2); + + check_int("diagnostic.suppressed.return", + run_adapter_with_diagnostics(&event, registry, 1, &result), + 31); + check_int("diagnostic.suppressed.result", result, + KZT_OBSERVATION_ADAPTER_UNCHANGED); + check_int("diagnostic.suppressed.calls", event.trace.diagnostic_calls, 2); + check_int("diagnostic.suppressed.legacy-calls", + event.trace.legacy_calls, 3); + + kzt_guest_registry_destroy(®istry); +} + +static void test_reader_failures_are_throttled(void) +{ + fake_callback_event_t event; + fake_read_failure_t failure; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_DISABLED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 1, + }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdiagnostic-fail.so", + 0x710000); + failure.addr = (uintptr_t)&event.link_map + + offsetof(struct link_map_x64, l_addr); + failure.size = sizeof(event.link_map.l_addr); + event.memory.failures = &failure; + event.memory.failure_count = 1; + event.trace.legacy_return = 37; + check_int("reader-diagnostic.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + check_int("reader-diagnostic.first-return", + run_adapter_with_diagnostics(&event, registry, 1, &result), + 37); + check_int("reader-diagnostic.first-result", result, + KZT_OBSERVATION_ADAPTER_READER_FAILED); + check_int("reader-diagnostic.first-calls", + event.trace.diagnostic_calls, 1); + check_int("reader-diagnostic.first-emitted", + event.trace.diagnostic_emitted, 1); + check_int("reader-diagnostic.first-legacy", + event.trace.legacy_calls, 1); + + check_int("reader-diagnostic.second-return", + run_adapter_with_diagnostics(&event, registry, 1, &result), + 37); + check_int("reader-diagnostic.second-result", result, + KZT_OBSERVATION_ADAPTER_READER_FAILED); + check_int("reader-diagnostic.second-calls", + event.trace.diagnostic_calls, 1); + check_int("reader-diagnostic.second-legacy", + event.trace.legacy_calls, 2); + + kzt_guest_registry_destroy(®istry); +} + +static void test_disabled_adapter_diagnostics_are_throttled(void) +{ + fake_callback_event_t event; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + kzt_observation_adapter_result_t result = KZT_OBSERVATION_ADAPTER_ADDED; + kzt_guest_registry_diagnostic_config_t config = { + .enabled = 1, + .throttle_limit = 1, + }; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + init_fake_callback_event(&event, "/guest/libdiagnostic-disabled.so", + 0x720000); + event.trace.legacy_return = 39; + check_int("disabled-diagnostic.configure", + kzt_guest_registry_configure_diagnostics(registry, &config), + 0); + + check_int("disabled-diagnostic.first-return", + run_adapter_with_diagnostics(&event, registry, 0, &result), + 39); + check_int("disabled-diagnostic.first-result", result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_int("disabled-diagnostic.first-calls", + event.trace.diagnostic_calls, 1); + check_int("disabled-diagnostic.first-emitted", + event.trace.diagnostic_emitted, 1); + check_int("disabled-diagnostic.first-legacy", + event.trace.legacy_calls, 1); + check_int("disabled-diagnostic.reader-calls", + event.trace.reader_calls, 0); + + check_int("disabled-diagnostic.second-return", + run_adapter_with_diagnostics(&event, registry, 0, &result), + 39); + check_int("disabled-diagnostic.second-result", result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_int("disabled-diagnostic.second-calls", + event.trace.diagnostic_calls, 1); + check_int("disabled-diagnostic.second-legacy", + event.trace.legacy_calls, 2); + check_int("disabled-diagnostic.second-reader-calls", + event.trace.reader_calls, 0); + + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_active_observation_adds_object_and_preserves_old_flow(); + test_dynamic_parser_success_commits_snapshot(); + test_dynamic_parser_read_failure_is_fail_open(); + test_dynamic_commit_failure_is_fail_open(); + test_disabled_adapter_skips_observation_but_preserves_old_flow(); + test_reader_failure_is_fail_open_for_old_flow(); + test_registry_failure_is_fail_open_for_old_flow(); + test_conflict_result_does_not_change_old_flow(); + test_no_callback_event_does_not_create_registry_objects(); + test_enabled_diagnostics_are_throttled_and_fail_open(); + test_reader_failures_are_throttled(); + test_disabled_adapter_diagnostics_are_throttled(); + + if (failures) { + fprintf(stderr, "kzt-observation-adapter: %d failure(s)\n", failures); + return 1; + } + + puts("kzt-observation-adapter: all contract tests passed"); + return 0; +} diff --git a/tests/unit/kzt/test_registry_diagnostics_gate.c b/tests/unit/kzt/test_registry_diagnostics_gate.c new file mode 100644 index 0000000000..005f3f5e5f --- /dev/null +++ b/tests/unit/kzt/test_registry_diagnostics_gate.c @@ -0,0 +1,300 @@ +#include +#include +#include + +#include "target/i386/latx/include/box64context.h" +#include "target/i386/latx/include/debug.h" +#include "target/i386/latx/include/kzt_observation_adapter.h" + +int relocation_log; +int kzt_registry_diagnostics; +int option_kzt; +int wine_option_kzt; + +typedef struct gate_trace { + int reader_calls; + int legacy_calls; + int diagnostic_outputs; + int legacy_return; + uintptr_t legacy_link_map_addr; + kzt_observation_adapter_result_t result; + kzt_observation_adapter_result_t diagnostic_result; + int diagnostic_emitted; +} gate_trace_t; + +typedef struct gate_fixture { + struct link_map_x64 link_map; + char guest_name[64]; + kzt_guest_link_map_reader_ops_t reader_ops; + gate_trace_t trace; +} gate_fixture_t; + +static int failures; + +static void check_true(const char *name, int condition) +{ + if (condition) { + return; + } + + fprintf(stderr, "%s: condition failed\n", name); + ++failures; +} + +static void check_int(const char *name, int got, int expected) +{ + if (got == expected) { + return; + } + + fprintf(stderr, "%s: got %d expected %d\n", name, got, expected); + ++failures; +} + +static int fake_read_memory(uintptr_t guest_addr, void *dst, size_t size, + void *opaque) +{ + gate_fixture_t *fixture = opaque; + uintptr_t base = (uintptr_t)&fixture->link_map; + size_t available = sizeof(*fixture) - + offsetof(gate_fixture_t, link_map); + + ++fixture->trace.reader_calls; + if (guest_addr < base || size > available || + guest_addr - base > available - size) { + return -1; + } + + memcpy(dst, (const void *)guest_addr, size); + return 0; +} + +static int fake_legacy_flow(uintptr_t link_map_addr, void *opaque) +{ + gate_trace_t *trace = opaque; + + ++trace->legacy_calls; + trace->legacy_link_map_addr = link_map_addr; + return trace->legacy_return; +} + +static void fake_registry_diagnostic_output( + const kzt_observation_adapter_diagnostic_t *diagnostic, + void *opaque) +{ + gate_trace_t *trace = opaque; + + ++trace->diagnostic_outputs; + trace->diagnostic_result = diagnostic->result; + trace->diagnostic_emitted = diagnostic->emitted; +} + +static void init_fixture(gate_fixture_t *fixture, const char *path, + uintptr_t load_bias) +{ + memset(fixture, 0, sizeof(*fixture)); + strcpy(fixture->guest_name, path); + fixture->link_map.l_addr = load_bias; + fixture->link_map.l_name = fixture->guest_name; + fixture->link_map.l_ld = (Elf64_Dyn *)(load_bias + 0x1000); + fixture->link_map.l_ns = 9; + fixture->link_map.l_map_start = load_bias; + fixture->link_map.l_map_end = load_bias + 0x20000; + fixture->reader_ops.read_memory = fake_read_memory; + fixture->reader_ops.opaque = fixture; + fixture->trace.legacy_return = 73; + fixture->trace.result = KZT_OBSERVATION_ADAPTER_DISABLED; +} + +static int kzt_active(void) +{ + return option_kzt || wine_option_kzt; +} + +static int run_gate_like_callback(gate_fixture_t *fixture, + kzt_guest_registry_t *registry) +{ + kzt_guest_registry_diagnostic_config_t diagnostic_config = { + .enabled = kzt_active(), + .throttle_limit = 1, + }; + kzt_observation_adapter_request_t request = { + .enabled = kzt_active(), + .diagnostics_enabled = kzt_registry_diagnostics_enabled(), + .link_map_addr = (uintptr_t)&fixture->link_map, + .registry = registry, + .reader_ops = &fixture->reader_ops, + .legacy_flow = fake_legacy_flow, + .legacy_opaque = &fixture->trace, + .diagnostic = fake_registry_diagnostic_output, + .diagnostic_opaque = &fixture->trace, + }; + + check_int("gate.configure", + kzt_guest_registry_configure_diagnostics(registry, + &diagnostic_config), + 0); + return kzt_observe_guest_object_from_callback(&request, + &fixture->trace.result); +} + +static void reset_options(void) +{ + relocation_log = LOG_NONE; + kzt_registry_diagnostics = 0; + option_kzt = 0; + wine_option_kzt = 0; +} + +static void assert_legacy_exactly_once(const char *name, + const gate_fixture_t *fixture) +{ + check_int(name, fixture->trace.legacy_calls, 1); + check_true("legacy.link-map", + fixture->trace.legacy_link_map_addr == + (uintptr_t)&fixture->link_map); +} + +static void test_gate_defaults_closed(void) +{ + reset_options(); + + check_int("default.diagnostics-enabled", + kzt_registry_diagnostics_enabled(), 0); +} + +static void test_diagnostics_option_alone_does_not_output(void) +{ + gate_fixture_t fixture; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + reset_options(); + kzt_registry_diagnostics = 1; + init_fixture(&fixture, "/guest/libkzt-off.so", 0x100000); + + ret = run_gate_like_callback(&fixture, registry); + + check_int("kzt-off.return", ret, 73); + check_int("kzt-off.result", fixture.trace.result, + KZT_OBSERVATION_ADAPTER_DISABLED); + check_int("kzt-off.diagnostic-outputs", + fixture.trace.diagnostic_outputs, 0); + check_int("kzt-off.reader-calls", fixture.trace.reader_calls, 0); + assert_legacy_exactly_once("kzt-off.legacy-calls", &fixture); + + kzt_guest_registry_destroy(®istry); +} + +static void test_active_kzt_without_diagnostics_does_not_output(void) +{ + gate_fixture_t fixture; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + reset_options(); + option_kzt = 2; + init_fixture(&fixture, "/guest/libdiagnostics-off.so", 0x200000); + + ret = run_gate_like_callback(&fixture, registry); + + check_int("diagnostics-off.return", ret, 73); + check_int("diagnostics-off.result", fixture.trace.result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostics-off.diagnostic-outputs", + fixture.trace.diagnostic_outputs, 0); + check_true("diagnostics-off.reader-ran", fixture.trace.reader_calls > 0); + assert_legacy_exactly_once("diagnostics-off.legacy-calls", &fixture); + + kzt_guest_registry_destroy(®istry); +} + +static void test_active_kzt_with_diagnostics_outputs(void) +{ + gate_fixture_t fixture; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + reset_options(); + option_kzt = 2; + kzt_registry_diagnostics = 1; + init_fixture(&fixture, "/guest/libdiagnostics-on.so", 0x300000); + + ret = run_gate_like_callback(&fixture, registry); + + check_int("diagnostics-on.return", ret, 73); + check_int("diagnostics-on.result", fixture.trace.result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostics-on.diagnostic-outputs", + fixture.trace.diagnostic_outputs, 1); + check_int("diagnostics-on.diagnostic-result", + fixture.trace.diagnostic_result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("diagnostics-on.diagnostic-emitted", + fixture.trace.diagnostic_emitted, 1); + check_true("diagnostics-on.reader-ran", fixture.trace.reader_calls > 0); + assert_legacy_exactly_once("diagnostics-on.legacy-calls", &fixture); + + kzt_guest_registry_destroy(®istry); +} + +static void test_debug_log_level_is_equivalent_diagnostics_option(void) +{ + gate_fixture_t fixture; + kzt_guest_registry_t *registry = kzt_guest_registry_init(); + int ret; + + check_true("registry.init", registry != NULL); + if (!registry) { + return; + } + + reset_options(); + option_kzt = 2; + relocation_log = LOG_DEBUG; + init_fixture(&fixture, "/guest/libdiagnostics-debug.so", 0x400000); + + ret = run_gate_like_callback(&fixture, registry); + + check_int("debug-log.return", ret, 73); + check_int("debug-log.result", fixture.trace.result, + KZT_OBSERVATION_ADAPTER_ADDED); + check_int("debug-log.diagnostic-outputs", + fixture.trace.diagnostic_outputs, 1); + assert_legacy_exactly_once("debug-log.legacy-calls", &fixture); + + kzt_guest_registry_destroy(®istry); +} + +int main(void) +{ + test_gate_defaults_closed(); + test_diagnostics_option_alone_does_not_output(); + test_active_kzt_without_diagnostics_does_not_output(); + test_active_kzt_with_diagnostics_outputs(); + test_debug_log_level_is_equivalent_diagnostics_option(); + + if (failures) { + fprintf(stderr, "kzt-registry-diagnostics-gate: %d failure(s)\n", + failures); + return 1; + } + + puts("kzt-registry-diagnostics-gate: all gate tests passed"); + return 0; +} diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 3bbb0e5647..4bffa5b1b2 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -6,3 +6,112 @@ test_elfload_pagesize = executable( ) test('test-elfload-pagesize', test_elfload_pagesize) + +kzt_guest_registry_test = executable( + 'kzt-guest-registry', + files( + 'kzt/test_guest_registry.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-registry', kzt_guest_registry_test) + +kzt_guest_registry_concurrency_test = executable( + 'kzt-guest-registry-concurrency', + files( + 'kzt/test_guest_registry_concurrency.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-registry-concurrency', kzt_guest_registry_concurrency_test) + +kzt_guest_link_map_reader_test = executable( + 'kzt-guest-link-map-reader', + files( + 'kzt/test_guest_link_map_reader.c', + '../../target/i386/latx/context/kzt_guest_link_map_reader.c', + ), + c_args: ['-DKZT_GUEST_LINK_MAP_READER_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-guest-link-map-reader', kzt_guest_link_map_reader_test) + +kzt_guest_dynamic_parser_test = executable( + 'kzt-guest-dynamic-parser', + files( + 'kzt/test_guest_dynamic_parser.c', + '../../target/i386/latx/context/kzt_guest_dynamic.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-guest-dynamic-parser', kzt_guest_dynamic_parser_test) + +kzt_guest_dynamic_snapshot_test = executable( + 'kzt-guest-dynamic-snapshot', + files( + 'kzt/test_guest_dynamic_snapshot.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-guest-dynamic-snapshot', kzt_guest_dynamic_snapshot_test) + +kzt_guest_dynamic_diagnostics_test = executable( + 'kzt-guest-dynamic-diagnostics', + files( + 'kzt/test_guest_dynamic_diagnostics.c', + '../../target/i386/latx/context/kzt_guest_dynamic_diagnostics.c', + ), + include_directories: include_directories('../..', '../../target/i386/latx/include'), +) + +test('kzt-guest-dynamic-diagnostics', kzt_guest_dynamic_diagnostics_test) + +kzt_observation_adapter_test = executable( + 'kzt-observation-adapter', + files( + 'kzt/test_observation_adapter.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + '../../target/i386/latx/context/kzt_guest_link_map_reader.c', + '../../target/i386/latx/context/kzt_observation_adapter.c', + '../../target/i386/latx/context/kzt_guest_dynamic.c', + ), + c_args: ['-DKZT_GUEST_REGISTRY_TEST', '-DKZT_GUEST_LINK_MAP_READER_TEST'], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-observation-adapter', kzt_observation_adapter_test) + +kzt_registry_diagnostics_gate_test = executable( + 'kzt-registry-diagnostics-gate', + files( + 'kzt/test_registry_diagnostics_gate.c', + '../../target/i386/latx/context/kzt_guest_registry.c', + '../../target/i386/latx/context/kzt_guest_link_map_reader.c', + '../../target/i386/latx/context/kzt_observation_adapter.c', + '../../target/i386/latx/context/kzt_guest_dynamic.c', + ), + c_args: [ + '-DCONFIG_LATX_KZT', + '-DKZT_GUEST_REGISTRY_TEST', + '-DKZT_GUEST_LINK_MAP_READER_TEST', + ], + include_directories: include_directories('../..', '../../target/i386/latx/include'), + dependencies: dependency('threads'), +) + +test('kzt-registry-diagnostics-gate', kzt_registry_diagnostics_gate_test)