aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoshua Bakita <bakitajoshua@gmail.com>2023-03-02 22:14:22 -0500
committerJoshua Bakita <bakitajoshua@gmail.com>2023-03-02 22:14:22 -0500
commit7db0d3088a6e25c7c64999a20267f55751571dee (patch)
tree9867d0ede3818ade3a63f942446b40d2f1446254
Initial reimplementation of libsmctrl as a library
- Tested working with cuda_scheduling_examiner - Supports everything described in the accepted RTAS'23 paper - Can be used as either a shared or staticly-linked library - Documented in libsmctrl.h
-rw-r--r--.gitignore4
-rw-r--r--Makefile21
-rw-r--r--libsmctrl.c332
-rw-r--r--libsmctrl.h62
-rw-r--r--libsmctrl_test_gpc_info.c14
5 files changed, 433 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dcff266
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
1libsmctrl.a
2libsmctrl.o
3libsmctrl.so
4libsmctrl_test_gpc_info
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..aa59792
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
1CC = gcc
2# -fPIC is needed in all cases, as we may be linked into another shared library
3CFLAGS = -fPIC
4LDFLAGS = -lcuda -I/usr/local/cuda/include
5
6.PHONY: clean tests
7
8libsmctrl.so: libsmctrl.c libsmctrl.h
9 $(CC) $< -shared -o $@ $(CFLAGS) $(LDFLAGS)
10
11libsmctrl.a: libsmctrl.c libsmctrl.h
12 $(CC) $< -c -o libsmctrl.o $(CFLAGS) $(LDFLAGS)
13 ar rcs $@ libsmctrl.o
14
15libsmctrl_test_gpc_info: libsmctrl_test_gpc_info.c
16 $(CC) $< -o $@ -L. -lsmctrl $(LDFLAGS)
17
18tests: libsmctrl_test_gpc_info
19
20clean:
21 rm -f libsmctrl.so libsmctrl.a
diff --git a/libsmctrl.c b/libsmctrl.c
new file mode 100644
index 0000000..69b19a1
--- /dev/null
+++ b/libsmctrl.c
@@ -0,0 +1,332 @@
1/**
2 * Copyright 2022 Joshua Bakita
3 * Library to control SM masks on CUDA launches. Co-opts preexisting debug
4 * logic in the CUDA driver library, and thus requires a build with -lcuda.
5 */
6
7//#include "/playpen/playpen/cuda-11.8/include/cuda.h"
8#include <cuda.h>
9//#include <cuda_runtime.h>
10//#ifndef CUDA_VERSION
11//#warning libsmctrl: CUDA driver library must be included before libsmctrl.h.
12//#endif
13
14#include <stdint.h>
15#include <errno.h>
16#include <fcntl.h>
17#include <unistd.h>
18#include <stdio.h>
19
20// Layout of mask control fields in CUDA's `globals` struct
21struct global_sm_control {
22 uint32_t enabled;
23 uint64_t mask;
24} __attribute__((packed));
25
26/*** CUDA Globals Manipulation. CUDA 10.2 only ***/
27
28// Ends up being 0x7fb7fa3408 in some binaries
29static struct global_sm_control* g_sm_control = NULL;
30
31/* Find the location of CUDA's `globals` struct and the SM mask control fields
32 * No symbols are exported from within `globals`, so this has to do a very
33 * messy lookup, following the pattern of the assembly of `cuDeviceGetCount()`.
34 * Don't call this before the cuda library has been initialized.
35 */
36static void setup_sm_control_10() {
37 if (g_sm_control)
38 return;
39 // Defeat relocation. cudbgReportDriverApiErrorFlags is relocated by
40 // the loader, but not subject to ASLR (it's always at a constant
41 // offset in the loaded instance of libcuda.so). Our target is also at
42 // a constant offset, so we can use the address of
43 // cudbgReportDriverApiErrorFlags as a reference point.
44 // Note: cudbgReportDriverApiErrorFlags is currently the closest known
45 // symbol to **the table**. cudbgDebuggerInitialized is the closest to
46 // globals itself (+7424 == SM mask control), but we perfer the table
47 // lookup approach for now, as that's what cuDeviceGetCount() does.
48 extern uint32_t cudbgReportDriverApiErrorFlags;
49 uint32_t* sym = 0;//&cudbgReportDriverApiErrorFlags;
50 // In some binaries, the following works out to 0x7fb7ea6000, and
51 // that's what shows up in the adrp instruction in cuDeviceGetCount()
52 // in the lead-up to get globals.numDevices. Find this offset by
53 // calling cuDeviceGetCount(0xdeadbeef), catching the segfault in GDB,
54 // disassembling the prior instructions, taking the adrp constant, and
55 // subtracting the address of cudbgReportDriverApiErrorFlags from it.
56 uintptr_t* tbl_base = (uintptr_t*)((uintptr_t)sym - 1029868);
57 // Address of `globals` is at offset 3672 (entry 459?)
58 uintptr_t globals_addr = *(tbl_base + 459); // Offset 3672 on aarch64
59 // SM mask control is at offset 4888 in the `globals` struct
60 g_sm_control = (struct global_sm_control*)(globals_addr + 4888);
61 // SM mask should be empty by default
62 if (g_sm_control->enabled || g_sm_control->mask)
63 fprintf(stderr, "Warning: Found non-NULL SM disable mask during setup! g_sm_control is likely invalid---use at own risk.\n");
64}
65
66/*** QMD/TMD-based SM Mask Control via Debug Callback. CUDA 11+ ***/
67
68// Tested working on CUDA x86_64 11.0-11.8.
69// Tested not working on aarch64 or x86_64 10.2
70static const CUuuid callback_funcs_id = {0x2c, (char)0x8e, 0x0a, (char)0xd8, 0x07, 0x10, (char)0xab, 0x4e, (char)0x90, (char)0xdd, 0x54, 0x71, (char)0x9f, (char)0xe5, (char)0xf7, 0x4b};
71#define LAUNCH_DOMAIN 0x3
72#define LAUNCH_PRE_UPLOAD 0x3
73static uint64_t g_sm_mask = 0;
74static __thread uint64_t g_next_sm_mask = 0;
75static char sm_control_setup_called = 0;
76static void launchCallback(void *ukwn, int domain, int cbid, const void *in_params) {
77 if (*(uint32_t*)in_params < 0x50) {
78 fprintf(stderr, "Unsupported CUDA version for callback-based SM masking. Aborting...\n");
79 return;
80 }
81 if (!**((uintptr_t***)in_params+8)) {
82 fprintf(stderr, "Called with NULL halLaunchDataAllocation\n");
83 return;
84 }
85 //fprintf(stderr, "cta: %lx\n", *(uint64_t*)(**((char***)in_params + 8) + 74));
86 // TODO: Check for supported QMD version (>XXX, <4.00)
87 // TODO: Support QMD version 4 (Hopper), where offset starts at +304 (rather than +84) and is 72 bytes (rather than 8 bytes) wide
88 uint32_t *lower_ptr = (uint32_t*)(**((char***)in_params + 8) + 84);
89 uint32_t *upper_ptr = (uint32_t*)(**((char***)in_params + 8) + 88);
90 if (g_next_sm_mask) {
91 *lower_ptr = (uint32_t)g_next_sm_mask;
92 *upper_ptr = (uint32_t)(g_next_sm_mask >> 32);
93 g_next_sm_mask = 0;
94 } else if (!*lower_ptr && !*upper_ptr){
95 // Only apply the global mask if a per-stream mask hasn't been set
96 *lower_ptr = (uint32_t)g_sm_mask;
97 *upper_ptr = (uint32_t)(g_sm_mask >> 32);
98 }
99 //fprintf(stderr, "lower mask: %x\n", *lower_ptr);
100 //fprintf(stderr, "upper mask: %x\n", *upper_ptr);
101}
102
103static void setup_sm_control_11() {
104 int (*subscribe)(uint32_t* hndl, void(*callback)(void*, int, int, const void*), void* ukwn);
105 int (*enable)(uint32_t enable, uint32_t hndl, int domain, int cbid);
106 uintptr_t* tbl_base;
107 uint32_t my_hndl;
108 // Avoid race conditions (setup can only be called once)
109 if (__atomic_test_and_set(&sm_control_setup_called, __ATOMIC_SEQ_CST))
110 return;
111
112 cuGetExportTable((const void**)&tbl_base, &callback_funcs_id);
113 uintptr_t subscribe_func_addr = *(tbl_base + 3);
114 uintptr_t enable_func_addr = *(tbl_base + 6);
115 subscribe = (typeof(subscribe))subscribe_func_addr;
116 enable = (typeof(enable))enable_func_addr;
117 int res = 0;
118 res = subscribe(&my_hndl, launchCallback, NULL);
119 if (res) {
120 fprintf(stderr, "libsmctrl: Error subscribing to launch callback. Error %d\n", res);
121 return;
122 }
123 res = enable(1, my_hndl, LAUNCH_DOMAIN, LAUNCH_PRE_UPLOAD);
124 if (res)
125 fprintf(stderr, "libsmctrl: Error enabling launch callback. Error %d\n", res);
126}
127
128// Common masking control
129void libsmctrl_set_global_mask(uint64_t mask) {
130 int ver;
131 cuDriverGetVersion(&ver);
132 if (ver <= 10020) {
133 if (!g_sm_control)
134 setup_sm_control_10();
135 g_sm_control->mask = mask;
136 g_sm_control->enabled = 1;
137 } else {
138 if (!sm_control_setup_called)
139 setup_sm_control_11();
140 g_sm_mask = mask;
141 }
142}
143
144void set_sm_mask(uint64_t mask) {
145 libsmctrl_set_global_mask(mask);
146}
147
148// Set mask for next launch from this thread
149void libsmctrl_set_next_mask(uint64_t mask) {
150 if (!sm_control_setup_called)
151 setup_sm_control_11();
152 g_next_sm_mask = mask;
153}
154
155
156/*** Per-Stream SM Mask (unlikely to be forward-compatible) ***/
157
158#define CU_8_0_MASK_OFF 0xec
159#define CU_9_0_MASK_OFF 0x130
160// CUDA 9.0 and 9.1 use the same offset
161#define CU_9_2_MASK_OFF 0x140
162#define CU_10_0_MASK_OFF 0x24c
163// CUDA 10.0, 10.1 and 10.2 use the same offset
164#define CU_11_0_MASK_OFF 0x274
165#define CU_11_1_MASK_OFF 0x2c4
166#define CU_11_2_MASK_OFF 0x37c
167// CUDA 11.2, 11.3, 11.4, and 11.5 use the same offset
168#define CU_11_6_MASK_OFF 0x38c
169#define CU_11_7_MASK_OFF 0x3c4
170#define CU_11_8_MASK_OFF 0x47c
171#define CU_12_0_MASK_OFF 0x4cc
172// CUDA 12.0 and 12.1 use the same offset
173
174// Layout in CUDA's `stream` struct
175struct stream_sm_mask {
176 uint32_t upper;
177 uint32_t lower;
178} __attribute__((packed));
179
180// Should work for CUDA 9.1, 10.0-11.8, 12.0-12.1
181// A cudaStream_t is a CUstream*. We use void* to avoid a cuda.h dependency in
182// our header
183void libsmctrl_set_stream_mask(void* stream, uint64_t mask) {
184 char* stream_struct_base = *(char**)stream;
185 struct stream_sm_mask* hw_mask;
186 int ver;
187 cuDriverGetVersion(&ver);
188 switch (ver) {
189 case 8000:
190 hw_mask = (struct stream_sm_mask*)(stream_struct_base + CU_8_0_MASK_OFF);
191 case 9000:
192 case 9010:
193 hw_mask = (struct stream_sm_mask*)(stream_struct_base + CU_9_0_MASK_OFF);
194 break;
195 case 9020: