libcuda — CUDA Runtime API Interception

CUDA Runtime API Interception Layer

Implements libcudart.so as a drop-in replacement for the NVIDIA CUDA runtime. Every CUDA API call from the application is intercepted and redirected into the GPGPU-Sim cycle-level simulator β€” memory allocation, data transfer, kernel launch, and synchronisation all run on the simulated GPU.

7
Source Files
CUDA
API Layer
80+
Functions Intercepted
C++
Language

LD_PRELOAD API Interception Kernel Launch Binary Parsing

πŸ’‘ What is libcuda?

Analogy: Think of libcuda as a customs checkpoint at a border crossing. Every CUDA call from your application tries to "cross the border" to the GPU. Instead of reaching real hardware, libcuda intercepts the call and routes it to GPGPU-Sim's simulated GPU β€” inspecting every packet (argument), stamping it (creating kernel_info_t), and forwarding it to the simulation pipeline.

Architecture

flowchart LR
  subgraph App["CUDA Application"]
    A1["cudaMalloc()"]
    A2["cudaMemcpy()"]
    A3["cudaLaunch()"]
  end
  subgraph Libcuda["libcuda / cuda_runtime_api.cc"]
    B1["Memory Manager\ng_mallocPtr_Size"]
    B2["Stream Manager\nCUstream_st"]
    B3["Kernel Resolver\nCUctx_st::m_kernel_lookup"]
    B4["Binary Loader\ncuobjdump parser"]
  end
  subgraph Core["GPGPU-Sim Core"]
    C1["gpgpu_sim::cycle()"]
    C2["stream_manager"]
  end
  A1 --> B1
  A2 --> B2
  A3 --> B3
  B4 --> B3
  B1 --> C2
  B2 --> C2
  B3 --> C2
  C2 --> C1

Data Flow β€” Kernel Launch Path

flowchart TD
  R["__cudaRegisterFatBinary()"] --> E["Extract PTX via cuobjdump"]
  E --> S["Build symbol_table"]
  S --> RF["__cudaRegisterFunction()\nMap hostFun β†’ function_info*"]
  RF --> CC["cudaConfigureCall()\nPush kernel_config onto stack"]
  CC --> SA["cudaSetupArgument()\nAppend gpgpu_ptx_sim_arg"]
  SA --> CL["cudaLaunch()\nResolve function_info*"]
  CL --> KI["Create kernel_info_t\n(grid, block, entry, stream)"]
  KI --> SM["stream_manager::push()\nstream_operation(kernel)"]
  SM --> SIM["gpgpu_sim_thread_concurrent\ngpu→launch(kernel)"]
  SIM --> CY["gpgpu_sim::cycle()\nShader pipeline β†’ Memory"]

🚢 Step-by-Step Narrated Walkthrough

Follow a single cudaLaunch(myKernel) call as it flows through the libcuda pipeline:

  1. Fat Binary Registration β€” At program start, __cudaRegisterFatBinary() extracts PTX from the CUDA fat binary using cuobjdump and builds a symbol_table.
  2. Function Registration β€” __cudaRegisterFunction() maps the host-side function pointer to a function_info* in CUctx_st::m_kernel_lookup.
  3. Configure Call β€” cudaConfigureCall(gridDim, blockDim, sharedMem, stream) pushes a kernel_config onto g_cuda_launch_stack.
  4. Setup Arguments β€” Each cudaSetupArgument() appends a gpgpu_ptx_sim_arg (pointer, size, offset) to the top config's argument list.
  5. Launch Resolution β€” cudaLaunch(hostFun) looks up function_info* via the host function pointer and pops the kernel config from the stack.
  6. Kernel Info Creation β€” A kernel_info_t is built with grid/block dims, the function entry point, arguments, and the target stream ID.
  7. Texture Snapshot β€” Texture binding state is copied into kernel_info_t so the kernel sees a consistent view.
  8. Stream Enqueue β€” stream_manager::push() places a stream_operation of type stream_kernel_launch into the command queue.
  9. Simulation Thread — gpgpu_sim_thread_concurrent() dequeues the operation and calls gpu→launch(kernel).
  10. Cycle Simulation β€” gpgpu_sim::cycle() runs the shader pipeline and memory hierarchy until all CTAs complete.

Key Classes & Structures

#Class / StructFileDescription
1gpgpu_contextgpgpu_context.hMaster singleton context. Owns pointers to cuda_runtime_api*, cuda_sim*, GPGPUsim_ctx*. Entry points: synchronize(), start_sim_thread().
2cuda_runtime_apicuda_api_object.hRuntime state: fatbinmap, name_symtab, pinned_memory, g_mallocPtr_Size. Key methods: cuobjdumpInit(), extract_code_using_cuobjdump().
3CUctx_stcuda_api_object.hCUDA context. Links host functions to device code via m_kernel_lookup: map<const void*, function_info*>. Stores per-binary m_code symbol tables.
4_cuda_device_idcuda_api_object.hDevice abstraction wrapping gpgpu_sim*. Methods: num_devices(), get_device(n), get_prop().
5kernel_configcuda_api_object.hPending launch state: m_GridDim, m_BlockDim, m_args (arg list), m_stream.
6CUevent_ststream_manager.hCUDA event object for timing and synchronisation.
7CUstream_ststream_manager.hCUDA stream β€” ordered queue of GPU operations.
8gpgpu_ptx_sim_argabstract_hardware_model.hKernel argument descriptor: m_start, m_nbytes, m_offset.
9function_infoptx_ir.hDecoded PTX kernel representation; looked up by name or host function pointer.
10symbol_tableptx_ir.hPTX symbol table β€” one per loaded binary; maps names to definitions.
11glbmap_entrycuda_api_object.hOpenGL interop buffer mapping: m_bufferObj, m_devPtr, m_size.

Key API Functions

CategoryFunctionPurpose
DevicecudaGetDeviceCount()Returns 1 (simulated GPU)
DevicecudaGetDeviceProperties()Fills cudaDeviceProp from sim config
MemorycudaMalloc()gpu_malloc() β€” increments from GLOBAL_HEAP_START
MemorycudaMemcpy()Dispatches H2D/D2H/D2D via stream_operation
MemorycudaMemcpyAsync()Queues memcpy on specified stream
MemorycudaFree()No-op (no garbage collection in simulator)
MemorycudaMallocHost()Pinned memory β€” host malloc + tracking map
LaunchcudaConfigureCall()Pushes kernel_config onto g_cuda_launch_stack
LaunchcudaSetupArgument()Appends gpgpu_ptx_sim_arg to config args
LaunchcudaLaunch()Resolves function_info, creates kernel_info_t, enqueues
SynccudaDeviceSynchronize()Global barrier β€” waits for all streams
StreamcudaStreamCreate()Creates CUstream_st object
EventcudaEventRecord()Records event in stream timeline
Register__cudaRegisterFatBinary()Extracts PTX from fat binary using cuobjdump
Register__cudaRegisterFunction()Maps host function pointer β†’ device kernel name

Module Interactions

Interacts WithDirectionMechanismPurpose
CUDA Application← inboundLD_PRELOAD / link-timeIntercepts all cuda*() API calls
cuobjdump_to_ptxplus→ outboundProcess invocationConverts fat binary to PTXPlus when enabled
cuda-sim (PTX parser)β†’ outboundgpgpu_ptx_sim_load_ptx_from_*Loads extracted PTX into functional simulator
stream_manager→ outboundpush(stream_operation)Enqueues kernel launches and memcpy operations
gpgpu_sim→ outboundgpu→launch(kernel)Hands off kernels for cycle-level simulation
gpgpusim_entrypoint↔ bothThread synchronisationManages simulation thread lifecycle

Configuration & Environment Variables

VariableTypeDescription
CUDA_INSTALL_PATHEnvPath to CUDA toolkit (required for cuobjdump)
GPGPUSIM_ROOTEnvSimulator root directory
PTX_SIM_MODE_FUNCEnv1 = functional-only; 0 = performance simulation
CUOBJDUMP_SIM_FILEEnvOverride binary path for cuobjdump extraction
PTX_SIM_USE_PTX_FILEEnvUse pre-extracted PTX file instead of fat binary
-gpgpu_ptx_use_cuobjdumpConfigEnable binary parsing via cuobjdump
-gpgpu_ptx_convert_to_ptxplusConfigEnable PTXPlus translation mode
-gpgpu_ptx_force_max_capabilityConfigForce a specific SM compute capability

Code Snippets

Kernel Launch β€” cudaLaunch() core logic

// cuda_runtime_api.cc β€” simplified cudaLaunch flow
cudaError_t cudaLaunch(const char *hostFun) {
  // 1. Resolve device function from host pointer
  CUctx_st *ctx = GPGPUSim_Context();
  function_info *entry = ctx->get_kernel(hostFun);

  // 2. Pop kernel_config from launch stack
  kernel_config config = g_cuda_launch_stack.back();
  g_cuda_launch_stack.pop_back();

  // 3. Create kernel_info_t with grid/block dims
  kernel_info_t *grid = new kernel_info_t(
      config.grid_dim(), config.block_dim(),
      entry, config.get_args());

  // 4. Enqueue into stream_manager
  stream_operation op(grid, sim_mode, config.get_stream());
  g_stream_manager->push(op);
  return cudaSuccess;
}

Memory Allocation β€” GPU address space

// Device memory allocation tracking
std::map<unsigned long long, size_t> g_mallocPtr_Size;

void *gpu_malloc(size_t size) {
  unsigned long long addr = m_dev_malloc;  // starts at GLOBAL_HEAP_START
  m_dev_malloc += size;
  // Align to 256 bytes
  if (size % 256) m_dev_malloc += (256 - size % 256);
  g_mallocPtr_Size[addr] = size;
  return (void *)addr;
}

Directory Map

Source tree for libcuda
libcuda/
β”œβ”€β”€ cuda_runtime_api.cc    ← 7,260 lines β€” implements 80+ cuda*() API functions
β”œβ”€β”€ cuda_api_object.h      ← Declares CUctx_st, _cuda_device_id, kernel_config
β”œβ”€β”€ cuda_api.h             ← Public header for cuda runtime API declarations
β”œβ”€β”€ gpgpu_context.h        ← Central gpgpu_context singleton class
β”œβ”€β”€ cuobjdump.h            ← cuobjdump parsing declarations
β”œβ”€β”€ cuobjdump.l            ← Flex lexer for cuobjdump output
β”œβ”€β”€ cuobjdump.y            ← Bison parser for cuobjdump output
β”œβ”€β”€ Makefile               ← Builds libcudart.so
└── CMakeLists.txt         ← CMake build configuration

Glossary

TermDefinition
Fat BinaryNVIDIA compiled binary containing PTX and/or cubin for multiple GPU architectures
cubinCompiled CUDA binary (SASS code) for a specific GPU compute capability
Host Function PointerCPU-side address of a __global__ kernel stub; used as lookup key to find device code
Symbol TableMaps PTX symbols (functions, variables) to their definitions and memory locations
Pinned MemoryNon-pageable host memory enabling faster DMA transfers to device
Stream OperationQueued GPU command (kernel launch, memcpy, event) in a CUDA stream