1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
|
#!/usr/bin/perl
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License, Version 1.0 only
# (the "License"). You may not use this file except in compliance
# with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# ident "%Z%%M% %I% %E% SMI"
#
#
# ctfstabs requires an object file with CTF data, and a file containing
# directives which indicate the types and members for which offsets are to
# be generated. The developer provides a single input file containing both
# the #include directives used to generate the object file as well as the
# offset directives. This script automates the splitting of the master file,
# the generation of the object file, the invocation of ctfstabs, and cleanup.
#
use strict;
use warnings;
use File::Basename;
use Getopt::Std;
use POSIX qw(:sys_wait_h);
# Globals.
our $PROGNAME = basename($0);
our ($CTmp, $OTmp, $GenTmp, $GenPPTmp, $Keep, $Verbose);
our ($SizeofTmp, $SizeofExe); # Hammerhead: sizeof helper temps
sub usage {
print STDERR "Usage: $PROGNAME [-k] [-s ctfstabs] [-r ctfconvert] ",
"compiler [options]\n";
print STDERR " NOTE: compiler options must enable stabs or DWARF as ",
"appropriate\n";
exit(2);
}
sub cleanup {
return if ($Keep);
unlink($CTmp) if (defined $CTmp && -f $CTmp);
unlink($OTmp) if (defined $OTmp && -f $OTmp);
unlink($GenTmp) if (defined $GenTmp && -f $GenTmp);
unlink($GenPPTmp) if (defined $GenPPTmp && -f $GenPPTmp);
# Hammerhead: Clean up sizeof helper files
unlink($SizeofTmp) if (defined $SizeofTmp && -f $SizeofTmp);
unlink($SizeofExe) if (defined $SizeofExe && -f $SizeofExe);
}
sub bail {
print STDERR "$PROGNAME: ", join(" ", @_), "\n";
cleanup();
exit(1);
}
sub findprog {
my ($arg, $name, $default) = @_;
if (defined $arg) {
return ($arg);
} elsif (defined $ENV{$name}) {
return ($ENV{$name});
} else {
return ($default);
}
}
sub runit {
my (@argv) = @_;
my $rc;
if ($Verbose) {
print STDERR "+ @argv\n";
}
if ((my $rc = system(@argv)) == -1) {
bail("Failed to execute $argv[0]: $!");
} elsif (WIFEXITED($rc)) {
$_ = WEXITSTATUS($rc);
if ($_ == 0) {
return;
} else {
bail("$argv[0] failed with status $_");
}
} elsif (WSIGNALLED($rc)) {
$_ = WTERMSIG($rc);
# WCOREDUMP isn't a POSIX macro, do it the non-portable way.
if ($rc & 0x80) {
bail("$argv[0] failed with signal $_ (core dumped)");
} else {
bail("$argv[0] failed with signal $_");
}
}
}
#
# Main.
#
my %opts;
getopts("kr:s:v", \%opts) || usage();
usage() if (@ARGV < 1);
my $ctfstabs = findprog($opts{"s"}, "CTFSTABS", "ctfstabs");
my $ctfconvert = findprog($opts{"r"}, "CTFCONVERT", "ctfconvert");
$Keep = $opts{k};
$Verbose = $opts{k} || $opts{v};
my ($cc, @cflags) = @ARGV;
$CTmp = "ctfstabs.tmp.$$.c"; # The C file used to generate CTF
$OTmp = "ctfstabs.tmp.$$.o"; # Object file with CTF
$GenTmp = "ctfstabs.tmp.$$.gen.c"; # genassym directives
$GenPPTmp = "ctfstabs.tmp.$$.genpp"; # Post-processed genassym directives
$SizeofTmp = "ctfstabs.tmp.$$.sizeof.c"; # Hammerhead: sizeof helper C file
$SizeofExe = "ctfstabs.tmp.$$.sizeof"; # Hammerhead: sizeof helper executable
my ($cfile, $genfile);
open($cfile, '>', $CTmp) || bail("failed to create $CTmp: $!");
open($genfile, '>', $GenTmp) || bail("failed to create $GenTmp: $!");
if ($Verbose) {
print STDERR "Splitting from stdin to $CTmp and $GenTmp\n";
}
# Hammerhead: Collect type names to force them into debug info
my @typenames;
# Hammerhead: Track types that only need sizeof (no member offsets)
# Format: { typename => SIZEOF_MACRO }
my %sizeof_only_types;
my @input_lines; # Save all input lines for parsing
# Hammerhead: Add __EXTENSIONS__ to enable all type definitions (e.g., sigjmp_buf)
print $cfile "#define __EXTENSIONS__ 1\n";
while (<STDIN>) {
push @input_lines, $_;
# #includes go to the C file. All other preprocessor directives
# go to both the C file and the offsets input file. Anything
# that's not a preprocessor directive goes into the offsets input
# file. Also strip comments from the genfile, as they can confuse
# the preprocessor.
if (/^#include/) {
print $cfile $_;
} elsif (/^#/) {
print $cfile $_;
print $genfile $_;
} elsif (/^\\#/) {
print $genfile $_;
} elsif (!/^\\/) {
# Hammerhead: Preserve blank lines using a marker that survives preprocessing.
# ctfstabs uses blank lines to separate type stanzas.
if (/^\s*$/) {
print $genfile "__GENOFFSETS_BLANK__\n";
} else {
print $genfile $_;
# Hammerhead: Capture type names (lines starting with non-whitespace
# that aren't preprocessor directives). Type names are at the start
# of lines, optionally followed by a size macro name.
if (/^([A-Za-z_][A-Za-z0-9_]*)(\s|$)/) {
push @typenames, $1;
}
}
}
}
# Hammerhead: Identify types that only need sizeof (no member fields following).
# These are types followed by a blank line or EOF, with only a size macro.
# Parse the input to find patterns like:
# typename SIZEOF_TYPENAME
# <blank line or EOF>
for (my $i = 0; $i < @input_lines; $i++) {
my $line = $input_lines[$i];
# Look for type lines with a size macro: "typename SIZEOF_*"
if ($line =~ /^([A-Za-z_][A-Za-z0-9_]*)\s+(SIZEOF_[A-Za-z0-9_]+)\s*$/) {
my ($typename, $sizemacro) = ($1, $2);
# Check if next non-comment, non-preprocessor line is blank or starts a new type
my $next_is_member = 0;
for (my $j = $i + 1; $j < @input_lines; $j++) {
my $nextline = $input_lines[$j];
next if $nextline =~ /^\\/ || $nextline =~ /^#/; # Skip comments/preprocessor
if ($nextline =~ /^\s+\S/) {
# Indented line = member field
$next_is_member = 1;
}
last; # Only check first relevant line
}
if (!$next_is_member) {
$sizeof_only_types{$typename} = $sizemacro;
if ($Verbose) {
print STDERR "Detected sizeof-only type: $typename -> $sizemacro\n";
}
}
}
}
# Note: We don't need to force types into debug info with dummy declarations.
# The types are already present in the CTF data from the compiled object,
# and ctfstabs's find_type() function handles struct/union/enum prefixes.
close($cfile) || bail("can't close $CTmp: $!");
close($genfile) || bail("can't close $GenTmp: $!");
# Compile the C file.
runit($cc, @cflags, '-c', '-o', $OTmp, $CTmp);
# Convert the debugging information to CTF.
runit($ctfconvert, '-l', 'ctfstabs', $OTmp);
# Run ctfstabs on the resulting mess.
# Hammerhead: Use -x assembler-with-cpp for preprocessing. GCC 14 otherwise fails
# on bare type names in the genassym directives (e.g. "proc PROCSIZE").
runit($cc, @cflags, "-x", "assembler-with-cpp", "-E", "-P", "-o", "$GenPPTmp.tmp", $GenTmp);
# Hammerhead: Restore blank lines from __GENOFFSETS_BLANK__ markers.
# GCC preprocessor strips blank lines, but ctfstabs needs them to separate type stanzas.
open(my $ppin, '<', "$GenPPTmp.tmp") || bail("can't open $GenPPTmp.tmp: $!");
open(my $ppout, '>', $GenPPTmp) || bail("can't create $GenPPTmp: $!");
while (<$ppin>) {
s/^__GENOFFSETS_BLANK__\s*$/\n/;
print $ppout $_;
}
close($ppin);
close($ppout);
unlink("$GenPPTmp.tmp") unless $Keep;
# Hammerhead: Generate sizeof values for types that don't have members.
# CTF/ctfstabs can't handle array typedefs (like sigjmp_buf), so we
# compute their sizes directly via a helper program.
my %sizeof_values;
if (%sizeof_only_types) {
if ($Verbose) {
print STDERR "Generating sizeof helper for: ", join(", ", keys %sizeof_only_types), "\n";
}
# Generate sizeof helper C program
open(my $sizeoffile, '>', $SizeofTmp) || bail("can't create $SizeofTmp: $!");
print $sizeoffile "#define __EXTENSIONS__ 1\n";
print $sizeoffile "#include <stdio.h>\n";
# Copy the #include lines from the original input
foreach my $line (@input_lines) {
if ($line =~ /^#include/) {
print $sizeoffile $line;
}
}
print $sizeoffile "int main(void) {\n";
foreach my $type (keys %sizeof_only_types) {
my $macro = $sizeof_only_types{$type};
print $sizeoffile "\tprintf(\"$macro\\t0x%lx\\n\", (unsigned long)sizeof($type));\n";
}
print $sizeoffile "\treturn 0;\n}\n";
close($sizeoffile);
# Compile and run the sizeof helper (use native compiler, not cross-compile flags)
# Filter out cross-compilation flags that would prevent running the helper
my @native_flags = grep { !/^-m64$/ && !/^-xarch/ && !/^-xmodel/ } @cflags;
# Remove -g flags (not needed for sizeof helper)
@native_flags = grep { !/^-g/ } @native_flags;
# Keep only include paths and defines
@native_flags = grep { /^-I/ || /^-D/ } @native_flags;
if ($Verbose) {
print STDERR "+ $cc @native_flags -o $SizeofExe $SizeofTmp\n";
}
my $rc = system($cc, @native_flags, '-o', $SizeofExe, $SizeofTmp);
if ($rc == 0) {
if ($Verbose) {
print STDERR "+ ./$SizeofExe\n";
}
open(my $sizeout, '-|', "./$SizeofExe") || bail("can't run $SizeofExe: $!");
while (<$sizeout>) {
chomp;
if (/^(\S+)\t(\S+)$/) {
$sizeof_values{$1} = $2;
if ($Verbose) {
print STDERR "Got sizeof: $1 = $2\n";
}
}
}
close($sizeout);
} else {
print STDERR "$PROGNAME: warning: sizeof helper failed, trying ctfstabs for all types\n";
}
# Clean up sizeof helper files
unless ($Keep) {
unlink($SizeofTmp) if -f $SizeofTmp;
unlink($SizeofExe) if -f $SizeofExe;
}
}
# Hammerhead: If we have sizeof values for types that ctfstabs can't handle,
# filter them out of the genpp file before running ctfstabs.
my $GenPPFiltered = $GenPPTmp;
if (%sizeof_values) {
$GenPPFiltered = "ctfstabs.tmp.$$.genpp.filtered";
open(my $gpin, '<', $GenPPTmp) || bail("can't open $GenPPTmp: $!");
open(my $gpout, '>', $GenPPFiltered) || bail("can't create $GenPPFiltered: $!");
my $skip_type = 0;
while (<$gpin>) {
# Check if this line starts a sizeof-only type that we've handled
if (/^([A-Za-z_][A-Za-z0-9_]*)\s+(SIZEOF_[A-Za-z0-9_]+)\s*$/) {
if (exists $sizeof_values{$2}) {
$skip_type = 1;
next;
}
}
# Blank line ends any type section
if (/^\s*$/) {
$skip_type = 0;
}
print $gpout $_ unless $skip_type;
}
close($gpin);
close($gpout);
}
# Run ctfstabs
runit($ctfstabs, "-t", "genassym", "-i", $GenPPFiltered, $OTmp);
# Hammerhead: Output sizeof values we computed directly
foreach my $macro (sort keys %sizeof_values) {
print "#define\t$macro\t$sizeof_values{$macro}\n";
}
# Clean up filtered file
unlink($GenPPFiltered) if ($GenPPFiltered ne $GenPPTmp && !$Keep);
cleanup();
exit (0);
|