diff --git a/src/asahi/lib/dyld_interpose.h b/src/asahi/lib/dyld_interpose.h new file mode 100644 index 00000000000..55f45d7a39f --- /dev/null +++ b/src/asahi/lib/dyld_interpose.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Macro found in WebKit source code: + * + * https://chromium.googlesource.com/chromium/blink.git/+/8ced00ca9f81cd8403584ae9123d9f3bb696c86a/Source/WebKit2/PluginProcess/mac/PluginProcessShim.mm + */ +#ifndef DYLD_INTERPOSE + +#define DYLD_INTERPOSE(_replacement,_replacee) \ + __attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee \ + __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee }; + +#endif diff --git a/src/asahi/lib/meson.build b/src/asahi/lib/meson.build index e921db1de25..856304c64bd 100644 --- a/src/asahi/lib/meson.build +++ b/src/asahi/lib/meson.build @@ -83,3 +83,16 @@ if with_tests protocol : gtest_test_protocol, ) endif + +if dep_iokit.found() + libasahi_wrap = shared_library( + 'wrap', + 'wrap.c', + include_directories : [inc_include, inc_src, inc_mapi, inc_mesa, inc_gallium, inc_gallium_aux, inc_asahi], + dependencies : [idep_mesautil, dep_iokit], + c_args : [no_override_init_args, '-Wno-missing-prototypes'], + gnu_symbol_visibility : 'hidden', + build_by_default : with_tools.contains('asahi'), + link_with: libasahi_decode, + ) +endif diff --git a/src/asahi/lib/wrap.c b/src/asahi/lib/wrap.c new file mode 100644 index 00000000000..86bff00eb86 --- /dev/null +++ b/src/asahi/lib/wrap.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2021-2022 Alyssa Rosenzweig + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util/compiler.h" +#include "io.h" +#include "decode.h" +#include "util.h" +#include "hexdump.h" +#include "dyld_interpose.h" + +/* + * Wrap IOKit entrypoints to intercept communication between the AGX kernel + * extension and userspace clients. IOKit prototypes are public from the IOKit + * source release. + */ + +mach_port_t metal_connection = 0; + +kern_return_t +wrap_Method(mach_port_t connection, uint32_t selector, const uint64_t* input, + uint32_t inputCnt, const void *inputStruct, size_t inputStructCnt, + uint64_t *output, uint32_t *outputCnt, void *outputStruct, + size_t *outputStructCntP) +{ + /* Heuristic guess which connection is Metal, skip over I/O from everything + * else. This is technically wrong but it works in practice, and reduces the + * surface area we need to wrap. + */ + if (selector == AGX_SELECTOR_SET_API) { + metal_connection = connection; + } else if (metal_connection != connection) { + return IOConnectCallMethod(connection, selector, input, inputCnt, + inputStruct, inputStructCnt, output, outputCnt, + outputStruct, outputStructCntP); + } + + printf("Selector %u, %X, %X\n", selector, connection, metal_connection); + + /* Check the arguments make sense */ + assert((input != NULL) == (inputCnt != 0)); + assert((inputStruct != NULL) == (inputStructCnt != 0)); + assert((output != NULL) == (outputCnt != 0)); + assert((outputStruct != NULL) == (outputStructCntP != 0)); + + /* Dump inputs */ + switch (selector) { + case AGX_SELECTOR_SET_API: + assert(input == NULL && output == NULL && outputStruct == NULL); + assert(inputStruct != NULL && inputStructCnt == 16); + assert(((uint8_t *) inputStruct)[15] == 0x0); + + printf("%X: SET_API(%s)\n", connection, (const char *) inputStruct); + break; + + case AGX_SELECTOR_ALLOCATE_MEM: { + const struct agx_allocate_resource_req *req = inputStruct; + struct agx_allocate_resource_req *req2 = (void *) inputStruct; + req2->mode = (req->mode & 0x800) | 0x430; + + bool suballocated = req->mode & 0x800; + + printf("Resource allocation:\n"); + printf(" Mode: 0x%X%s\n", req->mode & ~0x800, + suballocated ? " (suballocated) " : ""); + printf(" CPU fixed: 0x%" PRIx64 "\n", req->cpu_fixed); + printf(" CPU fixed (parent): 0x%" PRIx64 "\n", req->cpu_fixed_parent); + printf(" Size: 0x%X\n", req->size); + printf(" Flags: 0x%X\n", req->flags); + + if (suballocated) { + printf(" Parent: %u\n", req->parent); + } else { + assert(req->parent == 0); + } + + for (unsigned i = 0; i < ARRAY_SIZE(req->unk0); ++i) { + if (req->unk0[i]) + printf(" UNK%u: 0x%X\n", 0 + i, req->unk0[i]); + } + + for (unsigned i = 0; i < ARRAY_SIZE(req->unk6); ++i) { + if (req->unk6[i]) + printf(" UNK%u: 0x%X\n", 6 + i, req->unk6[i]); + } + + if (req->unk17) + printf(" UNK17: 0x%X\n", req->unk17); + + if (req->unk19) + printf(" UNK19: 0x%X\n", req->unk19); + + for (unsigned i = 0; i < ARRAY_SIZE(req->unk21); ++i) { + if (req->unk21[i]) + printf(" UNK%u: 0x%X\n", 21 + i, req->unk21[i]); + } + + break; + } + + case AGX_SELECTOR_SUBMIT_COMMAND_BUFFERS: + assert(output == NULL && outputStruct == NULL); + assert(inputStructCnt == sizeof(struct agx_submit_cmdbuf_req)); + assert(inputCnt == 1); + + printf("%X: SUBMIT_COMMAND_BUFFERS command queue id:%llx %p\n", + connection, input[0], inputStruct); + + const struct agx_submit_cmdbuf_req *req = inputStruct; + + agxdecode_cmdstream(req->command_buffer_shmem_id, + req->segment_list_shmem_id, true); + + if (getenv("ASAHI_DUMP")) + agxdecode_dump_mappings(req->segment_list_shmem_id); + + agxdecode_next_frame(); + FALLTHROUGH; + + default: + printf("%X: call %s (out %p, %zu)", connection, + wrap_selector_name(selector), outputStructCntP, + outputStructCntP ? *outputStructCntP : 0); + + for (uint64_t u = 0; u < inputCnt; ++u) + printf(" %llx", input[u]); + + if(inputStructCnt) { + printf(", struct:\n"); + hexdump(stdout, inputStruct, inputStructCnt, true); + } else { + printf("\n"); + } + + break; + } + + /* Invoke the real method */ + kern_return_t ret = + IOConnectCallMethod(connection, selector, input, inputCnt, inputStruct, + inputStructCnt, output, outputCnt, outputStruct, + outputStructCntP); + + if (ret != 0) + printf("return %u\n", ret); + + /* Track allocations for later analysis (dumping, disassembly, etc) */ + switch (selector) { + case AGX_SELECTOR_CREATE_SHMEM: { + assert(inputCnt == 2); + assert((*outputStructCntP) == 0x10); + uint64_t *inp = (uint64_t *) input; + + uint8_t type = inp[1]; + + assert(type <= 2); + if (type == 2) + printf("(cmdbuf with error reporting)\n"); + + uint64_t *ptr = (uint64_t *) outputStruct; + uint32_t *words = (uint32_t *) (ptr + 1); + + agxdecode_track_alloc(&(struct agx_bo) { + .handle = words[1], + .ptr.cpu = (void *) *ptr, + .size = words[0], + .type = inp[1] ? AGX_ALLOC_CMDBUF : AGX_ALLOC_MEMMAP + }); + + break; + } + + case AGX_SELECTOR_ALLOCATE_MEM: { + assert((*outputStructCntP) == 0x50); + const struct agx_allocate_resource_req *req = inputStruct; + struct agx_allocate_resource_resp *resp = outputStruct; + if (resp->cpu && req->cpu_fixed) + assert(resp->cpu == req->cpu_fixed); + printf("Response:\n"); + printf(" GPU VA: 0x%" PRIx64 "\n", resp->gpu_va); + printf(" CPU VA: 0x%" PRIx64 "\n", resp->cpu); + printf(" Handle: %u\n", resp->handle); + printf(" Root size: 0x%" PRIx64 "\n", resp->root_size); + printf(" Suballocation size: 0x%" PRIx64 "\n", resp->sub_size); + printf(" GUID: 0x%X\n", resp->guid); + for (unsigned i = 0; i < ARRAY_SIZE(resp->unk4); ++i) { + if (resp->unk4[i]) + printf(" UNK%u: 0x%X\n", 4 + i, resp->unk4[i]); + } + for (unsigned i = 0; i < ARRAY_SIZE(resp->unk11); ++i) { + if (resp->unk11[i]) + printf(" UNK%u: 0x%X\n", 11 + i, resp->unk11[i]); + } + + if (req->parent) + assert(resp->sub_size <= resp->root_size); + else + assert(resp->sub_size == resp->root_size); + + agxdecode_track_alloc(&(struct agx_bo) { + .type = AGX_ALLOC_REGULAR, + .size = resp->sub_size, + .handle = resp->handle, + .ptr.gpu = resp->gpu_va, + .ptr.cpu = (void *) resp->cpu, + }); + + break; + } + + case AGX_SELECTOR_FREE_MEM: { + assert(inputCnt == 1); + assert(inputStruct == NULL); + assert(output == NULL); + assert(outputStruct == NULL); + + agxdecode_track_free(&(struct agx_bo) { + .type = AGX_ALLOC_REGULAR, + .handle = input[0] + }); + + break; + } + + default: + /* Dump the outputs */ + if(outputCnt) { + printf("%u scalars: ", *outputCnt); + + for (uint64_t u = 0; u < *outputCnt; ++u) + printf("%llx ", output[u]); + + printf("\n"); + } + + if(outputStructCntP) { + printf(" struct\n"); + hexdump(stdout, outputStruct, *outputStructCntP, true); + + if (selector == 2) { + /* Dump linked buffer as well */ + void **o = outputStruct; + hexdump(stdout, *o, 64, true); + } + } + + printf("\n"); + break; + } + + return ret; +} + +kern_return_t +wrap_AsyncMethod(mach_port_t connection, uint32_t selector, + mach_port_t wakePort, uint64_t *reference, + uint32_t referenceCnt, const uint64_t *input, + uint32_t inputCnt, const void *inputStruct, + size_t inputStructCnt, uint64_t *output, uint32_t *outputCnt, + void *outputStruct, size_t *outputStructCntP) +{ + /* Check the arguments make sense */ + assert((input != NULL) == (inputCnt != 0)); + assert((inputStruct != NULL) == (inputStructCnt != 0)); + assert((output != NULL) == (outputCnt != 0)); + assert((outputStruct != NULL) == (outputStructCntP != 0)); + + printf("%X: call %X, wake port %X (out %p, %zu)", connection, selector, + wakePort, outputStructCntP, outputStructCntP ? *outputStructCntP : 0); + + for (uint64_t u = 0; u < inputCnt; ++u) + printf(" %llx", input[u]); + + if(inputStructCnt) { + printf(", struct:\n"); + hexdump(stdout, inputStruct, inputStructCnt, true); + } else { + printf("\n"); + } + + printf(", references: "); + for (unsigned i = 0; i < referenceCnt; ++i) + printf(" %llx", reference[i]); + printf("\n"); + + kern_return_t ret = IOConnectCallAsyncMethod(connection, selector, wakePort, + reference, referenceCnt, input, inputCnt, inputStruct, inputStructCnt, + output, outputCnt, outputStruct, outputStructCntP); + + printf("return %u", ret); + + if(outputCnt) { + printf("%u scalars: ", *outputCnt); + + for (uint64_t u = 0; u < *outputCnt; ++u) + printf("%llx ", output[u]); + + printf("\n"); + } + + if(outputStructCntP) { + printf(" struct\n"); + hexdump(stdout, outputStruct, *outputStructCntP, true); + + if (selector == 2) { + /* Dump linked buffer as well */ + void **o = outputStruct; + hexdump(stdout, *o, 64, true); + } + } + + printf("\n"); + return ret; +} + +kern_return_t +wrap_StructMethod(mach_port_t connection, uint32_t selector, + const void *inputStruct, size_t inputStructCnt, + void *outputStruct, size_t *outputStructCntP) +{ + return wrap_Method(connection, selector, NULL, 0, inputStruct, + inputStructCnt, NULL, NULL, outputStruct, + outputStructCntP); +} + +kern_return_t +wrap_AsyncStructMethod(mach_port_t connection, uint32_t selector, + mach_port_t wakePort, uint64_t *reference, + uint32_t referenceCnt, const void *inputStruct, + size_t inputStructCnt, void *outputStruct, + size_t *outputStructCnt) +{ + return wrap_AsyncMethod(connection, selector, wakePort, reference, + referenceCnt, NULL, 0, inputStruct, inputStructCnt, + NULL, NULL, outputStruct, outputStructCnt); +} + +kern_return_t +wrap_ScalarMethod(mach_port_t connection, uint32_t selector, + const uint64_t *input, uint32_t inputCnt, uint64_t *output, + uint32_t *outputCnt) +{ + return wrap_Method(connection, selector, input, inputCnt, NULL, 0, output, + outputCnt, NULL, NULL); +} + +kern_return_t +wrap_AsyncScalarMethod(mach_port_t connection, uint32_t selector, + mach_port_t wakePort, uint64_t *reference, + uint32_t referenceCnt, const uint64_t *input, + uint32_t inputCnt, uint64_t *output, uint32_t *outputCnt) +{ + return wrap_AsyncMethod(connection, selector, wakePort, reference, + referenceCnt, input, inputCnt, NULL, 0, output, + outputCnt, NULL, NULL); +} + +mach_port_t +wrap_DataQueueAllocateNotificationPort() +{ + mach_port_t ret = IODataQueueAllocateNotificationPort(); + printf("Allocated notif port %X\n", ret); + return ret; +} + +kern_return_t +wrap_SetNotificationPort(io_connect_t connect, uint32_t type, + mach_port_t port, uintptr_t reference) +{ + printf("Set noficiation port connect=%X, type=%X, port=%X, reference=%" + PRIx64"\n", connect, type, port, (uint64_t) reference); + + return IOConnectSetNotificationPort(connect, type, port, reference); +} + +IOReturn +wrap_DataQueueWaitForAvailableData(IODataQueueMemory *dataQueue, + mach_port_t notificationPort) +{ + printf("Waiting for data queue at notif port %X\n", notificationPort); + IOReturn ret = IODataQueueWaitForAvailableData(dataQueue, notificationPort); + printf("ret=%X\n", ret); + return ret; +} + +IODataQueueEntry * +wrap_DataQueuePeek(IODataQueueMemory *dataQueue) +{ + printf("Peeking data queue\n"); + return IODataQueuePeek(dataQueue); +} + +IOReturn +wrap_DataQueueDequeue(IODataQueueMemory *dataQueue, void *data, uint32_t *dataSize) +{ + printf("Dequeueing (dataQueue=%p, data=%p, buffer %u)\n", dataQueue, data, *dataSize); + IOReturn ret = IODataQueueDequeue(dataQueue, data, dataSize); + printf("Return \"%s\", got %u bytes\n", mach_error_string(ret), *dataSize); + + uint8_t *data8 = data; + for (unsigned i = 0; i < *dataSize; ++i) { + printf("%02X ", data8[i]); + } + printf("\n"); + + return ret; +} + +DYLD_INTERPOSE(wrap_Method, IOConnectCallMethod); +DYLD_INTERPOSE(wrap_AsyncMethod, IOConnectCallAsyncMethod); +DYLD_INTERPOSE(wrap_StructMethod, IOConnectCallStructMethod); +DYLD_INTERPOSE(wrap_AsyncStructMethod, IOConnectCallAsyncStructMethod); +DYLD_INTERPOSE(wrap_ScalarMethod, IOConnectCallScalarMethod); +DYLD_INTERPOSE(wrap_AsyncScalarMethod, IOConnectCallAsyncScalarMethod); +DYLD_INTERPOSE(wrap_SetNotificationPort, IOConnectSetNotificationPort); +DYLD_INTERPOSE(wrap_DataQueueAllocateNotificationPort, IODataQueueAllocateNotificationPort); +DYLD_INTERPOSE(wrap_DataQueueWaitForAvailableData, IODataQueueWaitForAvailableData); +DYLD_INTERPOSE(wrap_DataQueuePeek, IODataQueuePeek); +DYLD_INTERPOSE(wrap_DataQueueDequeue, IODataQueueDequeue);