maxas/MaxAs/Cubin.pm (519 lines of code) (raw):

package MaxAs::Cubin; use strict; use Data::Dumper; my @Elf32_Hdr = qw( H8 magic C fileClass C encoding C fileVersion H18 padding S type S machine L version L entry L phOffset L shOffset L flags S ehSize S phEntSize S phNum S shEntSize S shNum S shStrIndx ); my @Elf64_Hdr = qw( H8 magic C fileClass C encoding C fileVersion H18 padding S type S machine L version Q entry Q phOffset Q shOffset L flags S ehSize S phEntSize S phNum S shEntSize S shNum S shStrIndx ); my @Elf32_PrgHdr = qw( L type L offset L vaddr L paddr L fileSize L memSize L flags L align ); my @Elf64_PrgHdr = qw( L type L flags Q offset Q vaddr Q paddr Q fileSize Q memSize Q align ); my @Elf32_SecHdr = qw( L name L type L flags L addr L offset L size L link L info L align L entSize ); my @Elf64_SecHdr = qw( L name L type Q flags Q addr Q offset Q size L link L info Q align Q entSize ); my @Elf32_SymEnt = qw( L name L value L size C info C other S shIndx ); my @Elf64_SymEnt = qw( L name C info C other S shIndx Q value Q size ); my @symBind = qw(LOCAL GLOBAL WEAK); # Split the Elf Header defs into template strings (T) and corresponding hash keys columns (C) my (@elfHdrT, @prgHdrT, @secHdrT, @symHdrT, @elfHdrC, @prgHdrC, @secHdrC, @symHdrC); $elfHdrT[1] = join '', grep { length($_) <= 3} @Elf32_Hdr; $prgHdrT[1] = join '', grep { length($_) <= 3} @Elf32_PrgHdr; $secHdrT[1] = join '', grep { length($_) <= 3} @Elf32_SecHdr; $symHdrT[1] = join '', grep { length($_) <= 3} @Elf32_SymEnt; $elfHdrT[2] = join '', grep { length($_) <= 3} @Elf64_Hdr; $prgHdrT[2] = join '', grep { length($_) <= 3} @Elf64_PrgHdr; $secHdrT[2] = join '', grep { length($_) <= 3} @Elf64_SecHdr; $symHdrT[2] = join '', grep { length($_) <= 3} @Elf64_SymEnt; $elfHdrC[1] = [ grep { length($_) > 3} @Elf32_Hdr ]; $prgHdrC[1] = [ grep { length($_) > 3} @Elf32_PrgHdr ]; $secHdrC[1] = [ grep { length($_) > 3} @Elf32_SecHdr ]; $symHdrC[1] = [ grep { length($_) > 3} @Elf32_SymEnt ]; $elfHdrC[2] = [ grep { length($_) > 3} @Elf64_Hdr ]; $prgHdrC[2] = [ grep { length($_) > 3} @Elf64_PrgHdr ]; $secHdrC[2] = [ grep { length($_) > 3} @Elf64_SecHdr ]; $symHdrC[2] = [ grep { length($_) > 3} @Elf64_SymEnt ]; # Load a cubin ELF file sub new { my ($package, $file) = @_; my $cubin = bless { fileName => $file }, $package; open my $fh, $file or die "$file: $!"; binmode($fh); # Read in assuming 32 bit header my $data; read $fh, $data, 0x34; my $elfHdr = $cubin->{elfHdr} = {}; @{$elfHdr}{@{$elfHdrC[1]}} = unpack $elfHdrT[1], $data; # 1: 32bit, 2: 64bit my $class = $elfHdr->{fileClass}; # re-read in with 64 bit header if needed if ($class == 2) { seek $fh, 0, 0; read $fh, $data, 0x46; @{$elfHdr}{@{$elfHdrC[$class]}} = unpack $elfHdrT[$class], $data; $cubin->{Class} = 64; } else { $cubin->{Class} = 32; } # verify sm_50 cubin $cubin->{Arch} = $elfHdr->{flags} & 0xFF; die "Cubin not in sm_50 or greater format. Found: sm_$cubin->{Arch}\n" if $cubin->{Arch} < 50; $cubin->{AddressSize} = $elfHdr->{flags} & 0x400 ? 64 : 32; # Read in Program Headers seek $fh, $elfHdr->{phOffset}, 0; foreach (1 .. $elfHdr->{phNum}) { read $fh, $data, $elfHdr->{phEntSize}; my %prgHdr = (Indx => $_ - 1); @prgHdr{@{$prgHdrC[$class]}} = unpack $prgHdrT[$class], $data; push @{$cubin->{prgHdrs}}, \%prgHdr; } # Read in Section Headers seek $fh, $elfHdr->{shOffset}, 0; foreach (1 .. $elfHdr->{shNum}) { read $fh, $data, $elfHdr->{shEntSize}; my %secHdr = (Indx => $_ - 1); @secHdr{@{$secHdrC[$class]}} = unpack $secHdrT[$class], $data; push @{$cubin->{secHdrs}}, \%secHdr; } # Read in Section data foreach my $secHdr (@{$cubin->{secHdrs}}) { $data = ''; # Skip sections with no data (type NULL or NOBITS) if ($secHdr->{size} && $secHdr->{type} != 8) { seek $fh, $secHdr->{offset}, 0; read $fh, $data, $secHdr->{size}; } # Convert string tables to maps if ($secHdr->{type} == 3) # STRTAB { my $strTab = $secHdr->{StrTab} = {}; my $indx = 0; foreach my $str (split "\0", $data) { $strTab->{$indx} = $str; $indx += 1 + length($str); } } # Read in Symbol data if ($secHdr->{type} == 2) # SYMTAB { my $offset = 0; while ($offset < $secHdr->{size}) { my $symEnt = {}; @{$symEnt}{@{$symHdrC[$class]}} = unpack $symHdrT[$class], substr($data, $offset, $secHdr->{entSize}); $offset += $secHdr->{entSize}; push @{$secHdr->{SymTab}}, $symEnt; } } # Cache raw data for further processing and writing $secHdr->{Data} = unpack 'H*', $data; } close $fh; # Update section headers with their names. Map names directly to headers. my $shStrTab = $cubin->{secHdrs}[$elfHdr->{shStrIndx}]{StrTab}; foreach my $secHdr (@{$cubin->{secHdrs}}) { $secHdr->{Name} = $shStrTab->{$secHdr->{name}}; $cubin->{$secHdr->{Name}} = $secHdr; } # Update symbols with their names # For the Global functions, extract kernel meta data # Populate the kernel hash my $strTab = $cubin->{'.strtab'}{StrTab}; foreach my $symEnt (@{$cubin->{'.symtab'}{SymTab}}) { $symEnt->{Name} = $strTab->{$symEnt->{name}}; # Attach symbol to section my $secHdr = $cubin->{secHdrs}[$symEnt->{shIndx}]; $secHdr->{SymbolEnt} = $symEnt; # Look for symbols tagged FUNC if (($symEnt->{info} & 0x0f) == 0x02) { # Create a hash of kernels for output my $kernelSec = $cubin->{Kernels}{$symEnt->{Name}} = $secHdr; # Extract local/global/weak binding info $kernelSec->{Linkage} = $symBind[($symEnt->{info} & 0xf0) >> 4]; # Extract the kernel instructions $kernelSec->{KernelData} = [ unpack "Q*", pack "H*", $kernelSec->{Data} ]; # Extract the max barrier resource identifier used and add 1. Should be 0-16. # If a register is used as a barrier resource id, then this value is the max of 16. $kernelSec->{BarCnt} = ($kernelSec->{flags} & 0x01f00000) >> 20; # Extract the number of allocated registers for this kernel. $kernelSec->{RegCnt} = ($kernelSec->{info} & 0xff000000) >> 24; # Extract the size of shared memory this kernel uses. my $sharedSec = $kernelSec->{SharedSec} = $cubin->{".nv.shared.$symEnt->{Name}"}; $kernelSec->{SharedSize} = $sharedSec ? $sharedSec->{size} : 0; # Attach constant0 section $kernelSec->{ConstantSec} = $cubin->{".nv.constant0.$symEnt->{Name}"}; # Extract the kernel parameter data. my $paramSec = $kernelSec->{ParamSec} = $cubin->{".nv.info.$symEnt->{Name}"}; if ($paramSec) { # Extract raw param data my @data = unpack "L*", pack "H*", $paramSec->{Data}; $paramSec->{ParamData} = \@data; $paramSec->{ParamHex} = [ map { sprintf '0x%08x', $_ } @data ]; # Find the first param delimiter my $idx = 0; $idx++ while $idx < @data && $data[$idx] != 0x00080a04; my $first = $data[$idx+2] & 0xFFFF; #my $size = $data[$idx+2] >> 16; $idx += 4; my @params; while ($idx < @data && $data[$idx] == 0x000c1704) { # Get the ordinal, offset, size and pointer alignment for each param my $ord = $data[$idx+2] & 0xFFFF; my $offset = sprintf '0x%02x', $first + ($data[$idx+2] >> 16); my $psize = $data[$idx+3] >> 18; my $align = $data[$idx+3] & 0x400 ? 1 << ($data[$idx+3] & 0x3ff) : 0; unshift @params, "$ord:$offset:$psize:$align"; $idx += 4; } my @staticParams = @data[0 .. ($idx-1)]; my ($maxregCount, @exitOffsets, @ctaidOffsets, $ctaidzUsed, @reqntid, @maxntid, @stackSize); while ($idx < @data) { my $code = $data[$idx] & 0xffff; my $size = $data[$idx] >> 16; $idx++; # EIATTR_MAXREG_COUNT if ($code == 0x1b03) { $maxregCount = $size; } # EIATTR_S2RCTAID_INSTR_OFFSETS elsif ($code == 0x1d04) { while ($size > 0) { push @ctaidOffsets, $data[$idx++]; $size -= 4; } } # EIATTR_EXIT_INSTR_OFFSETS elsif ($code == 0x1c04) { while ($size > 0) { push @exitOffsets, $data[$idx++]; $size -= 4; } } # EIATTR_CTAIDZ_USED elsif ($code == 0x0401) { $ctaidzUsed = 1; } # EIATTR_REQNTID elsif ($code == 0x1004) { while ($size > 0) { push @reqntid, $data[$idx++]; $size -= 4; } } # EIATTR_MAX_THREADS elsif ($code == 0x0504) { while ($size > 0) { push @maxntid, $data[$idx++]; $size -= 4; } } # EIATTR_CRS_STACK_SIZE elsif ($code == 0x1e04) { while ($size > 0) { push @stackSize, $data[$idx++]; $size -= 4; } } else { printf STDERR "Unknown Code 0x%02x (size:%d)\n", $code, $size; } } $kernelSec->{Params} = \@params; $kernelSec->{ParamCnt} = scalar @params; $paramSec->{StaticParams} = \@staticParams; $paramSec->{MAXREG_COUNT} = $maxregCount; $paramSec->{ExitOffsets} = \@exitOffsets; $paramSec->{CTAIDOffsets} = \@ctaidOffsets; $paramSec->{CTAIDZUsed} = $ctaidzUsed; $paramSec->{REQNTID} = \@reqntid; $paramSec->{MAXNTID} = \@maxntid; $paramSec->{STACKSIZE} = \@stackSize; } # print Dumper($paramSec); # exit(); } # Note GLOBALs found in this cubin elsif (($symEnt->{info} & 0x10) == 0x10) { $cubin->{Symbols}{$symEnt->{Name}} = $symEnt; } } # print "phOffset: $elfHdr->{phOffset}\n"; # print "shOffset: $elfHdr->{shOffset}\n"; # foreach my $secHdr (@{$cubin->{secHdrs}}) # { # print "secHdr($secHdr->{Indx}): $secHdr->{offset}, $secHdr->{size}, $secHdr->{align} ($secHdr->{Name})\n"; # } # my $p = 0; # foreach my $prgHdr (@{$cubin->{prgHdrs}}) # { # print "prgHdr($p): type: $prgHdr->{type}, offset: $prgHdr->{offset}, fileSize: $prgHdr->{fileSize}, memSize: $prgHdr->{memSize}, align: $prgHdr->{align}\n"; # $p++; # } # exit(); #print map { sprintf "%016x\n", $_ } @{$cubin->{Kernels}{microbench}{KernelData}}; #print Dumper($cubin->{Kernels}{test}{KernelData}); #exit(); return $cubin; } sub class { return shift()->{Class}; } sub arch { return shift()->{Arch}; } sub address_size { return shift()->{AddressSize}; } sub listKernels { return shift()->{Kernels}; } sub listSymbols { return shift()->{Symbols}; } sub getKernel { my ($cubin, $kernel) = @_; return $cubin->{Kernels}{$kernel}; } sub modifyKernel { my ($cubin, %params) = @_; my $kernelSec = $params{Kernel}; my $newReg = $params{RegCnt}; my $newBar = $params{BarCnt}; my $exitOffsets = $params{ExitOffsets}; my $ctaidOffsets = $params{CTAIDOffsets}; my $ctaidzUsed = $params{CTAIDZUsed}; my $newData = $params{KernelData}; my $newSize = @$newData * 8; die "255 register max" if $newReg > 255; die "new kernel size must be multiple of 8 instructions (64 bytes)" if $newSize & 63; die "16 is max barrier count" if $newBar > 16; my $paramSec = $kernelSec->{ParamSec}; my $kernelName = $kernelSec->{SymbolEnt}{Name}; my $maxregCount = $paramSec->{MAXREG_COUNT}; my $stackSize = $paramSec->{STACKSIZE}; # update the kernel $kernelSec->{KernelData} = $newData; $kernelSec->{Data} = unpack "H*", pack "Q*", @$newData; if ($newReg != $kernelSec->{RegCnt}) { print "Modified $kernelName RegCnt: $kernelSec->{RegCnt} => $newReg\n"; $kernelSec->{RegCnt} = $newReg; $kernelSec->{info} &= ~0xff000000; $kernelSec->{info} |= $newReg << 24; } if ($newBar != $kernelSec->{BarCnt}) { print "Modified $kernelName BarCnt: $kernelSec->{BarCnt} => $newBar\n"; $kernelSec->{BarCnt} = $newBar; $kernelSec->{flags} &= ~0x01f00000; $kernelSec->{flags} |= $newBar << 20; } my @paramData = @{$paramSec->{StaticParams}}; if (defined $maxregCount) { push @paramData, ($maxregCount << 16) | 0x1b03; } my $newCTAIDs = join ',', map { sprintf '%04x', $_ } @$ctaidOffsets; my $oldCTAIDs = join ',', map { sprintf '%04x', $_ } @{$paramSec->{CTAIDOffsets}}; if ($newCTAIDs ne $oldCTAIDs) { print "Modified $kernelName CTAID Offsets: '$oldCTAIDs' => '$newCTAIDs'\n"; } if (@$ctaidOffsets) { push @paramData, (scalar(@$ctaidOffsets) << 18) | 0x1d04; push @paramData, @$ctaidOffsets; } my $newExits = join ',', map { sprintf '%04x', $_ } @$exitOffsets; my $oldExits = join ',', map { sprintf '%04x', $_ } @{$paramSec->{ExitOffsets}}; if ($newExits ne $oldExits) { print "Modified $kernelName Exit Offsets: '$oldExits' => '$newExits'\n"; } if (@$exitOffsets) { push @paramData, (scalar(@$exitOffsets) << 18) | 0x1c04; push @paramData, @$exitOffsets; } if ($ctaidzUsed != $paramSec->{CTAIDZUsed}) { print "Modified $kernelName CTAID.Z Used: '$paramSec->{CTAIDZUsed}' => '$ctaidzUsed'\n"; } if ($ctaidzUsed) { push @paramData, 0x0401; } if (@{$paramSec->{REQNTID}}) { push @paramData, (scalar(@{$paramSec->{REQNTID}}) << 18) | 0x1004; push @paramData, @{$paramSec->{REQNTID}}; } if (@{$paramSec->{MAXNTID}}) { push @paramData, (scalar(@{$paramSec->{MAXNTID}}) << 18) | 0x0504; push @paramData, @{$paramSec->{MAXNTID}}; } if (@$stackSize) { push @paramData, (scalar(@$stackSize) << 18) | 0x1e04; push @paramData, @$stackSize; } my $newParamSize = scalar(@paramData)*4; $paramSec->{Data} = unpack "H*", pack "L*", @paramData; if ($newParamSize != $paramSec->{size}) { print "Modified $kernelName ParamSecSize: $paramSec->{size} => $newParamSize\n"; $cubin->updateSize($paramSec, $newParamSize); } if ($newSize != $kernelSec->{size}) { print "Modified $kernelName KernelSize: $kernelSec->{size} => $newSize\n"; $cubin->updateSize($kernelSec, $newSize, 1); } } sub updateSize { my ($cubin, $sec, $newSize, $updatePrgSize) = @_; my $elfHdr = $cubin->{elfHdr}; my $class = $elfHdr->{fileClass}; # update section header my $delta = $newSize - $sec->{size}; $sec->{size} = $newSize; # update symtab section if ($sec->{SymbolEnt}) { $sec->{SymbolEnt}{size} = $newSize; my $symSection = $cubin->{'.symtab'}; $symSection->{Data} = ''; foreach my $symEnt (@{$symSection->{SymTab}}) { $symSection->{Data} .= unpack "H*", pack $symHdrT[$class], @{$symEnt}{@{$symHdrC[$class]}}; } } my $pos = $elfHdr->{ehSize}; my %sizeMap; # update section header offsets foreach my $secHdr (@{$cubin->{secHdrs}}) { # skip first header next if $secHdr->{align} == 0; # NOBITS data sections are size 0 my $size = $secHdr->{type} == 8 ? 0 : $secHdr->{size}; # Add any needed padding between sections my $pad = $pos % $secHdr->{align}; if ($pad > 0) { $pos += $secHdr->{align} - $pad; } # map old offset to new $sizeMap{$secHdr->{offset}} = $pos; # update offset $secHdr->{offset} = $pos; # advance position by size $pos += $size; } # compute total section header size my $shSize = $elfHdr->{phOffset} - $elfHdr->{shOffset}; # map old offset to new $sizeMap{$elfHdr->{shOffset}} = $pos; $sizeMap{$elfHdr->{phOffset}} = $pos + $shSize; $elfHdr->{shOffset} = $pos; $elfHdr->{phOffset} = $pos + $shSize; # update program header offsets and sizes foreach my $prgHdr (@{$cubin->{prgHdrs}}) { # Not sure how best to adjust these so just assume they'll track other offsets. $prgHdr->{offset} = $sizeMap{$prgHdr->{offset}}; # If the kernel sizes changes, also update the associated ProgramHeader. # Note that this size is the kernel size plus any constant section sizes. if ($updatePrgSize && $prgHdr->{type} == 1 && $sec->{offset} >= $prgHdr->{offset} && $sec->{offset} < $prgHdr->{offset} + $prgHdr->{fileSize} + $delta) { $prgHdr->{fileSize} += $delta; $prgHdr->{memSize} += $delta; } } } # Write out the cubin after modifying it. sub write { my ($cubin, $file) = @_; open my $fh, ">$file" or die "Error: could not open $file for writing: $!"; binmode($fh); my $elfHdr = $cubin->{elfHdr}; my $class = $elfHdr->{fileClass}; # write elf header print $fh pack $elfHdrT[$class], @{$elfHdr}{@{$elfHdrC[$class]}}; my $pos = $elfHdr->{ehSize}; # write section data foreach my $secHdr (@{$cubin->{secHdrs}}) { # Skip NULL and NOBITS data sections next if $secHdr->{size} == 0 || $secHdr->{type} == 8; # Add any needed padding between sections my $pad = $pos % $secHdr->{align}; if ($pad > 0) { $pad = $secHdr->{align} - $pad; print $fh join '', "\0" x $pad; $pos += $pad; } print $fh pack 'H*', $secHdr->{Data}; $pos += $secHdr->{size}; } # write section headers foreach my $secHdr (@{$cubin->{secHdrs}}) { print $fh pack $secHdrT[$class], @{$secHdr}{@{$secHdrC[$class]}}; } #write program headers foreach my $prgHdr (@{$cubin->{prgHdrs}}) { print $fh pack $prgHdrT[$class], @{$prgHdr}{@{$prgHdrC[$class]}}; } close $fh; } __END__