C variables representation in assembly(gcc -S)
In this article I'll try to find out what C vars(symbols) are in the assembly generated by gcc -S
.
On Raspiberry PI 3 Model B/aarch64/ubuntu, compile with gcc
to produce assembly code:
-S
: Stop after the stage of compilation proper; do not assemble.
The output is in the form of an assembler code file for each non-assembler input file specified.
By default, the assembler file name for a source file is made by replacing the suffix ‘.c’, ‘.i’, etc., with ‘.s
’.
-fverbose-asm
: Put extra commentary information in the generated assembly code to make it more readable.
asm#
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 |
|
vars#
Refer to GNU Assembler Directives:
.type name,type_description
: sets the type of a symbol name to be either a function or an object. Valid values for type_desription in GNU AArch64 assembly include:
%function
: The symbol is a function name.%object
: The symbol is a data object.%common
: The symbol is a common (shared) object.
int i#
Uninitialized global int i
will be initialized with 0 and placed in the .bss
section:
- .type = %object: data object
- .size = 4 Byte
- .align = 2 => 2^2: 4 Byte
short j = 2#
Initialized global short j = 2;
is placed in the .data
section:
- .type = %object: data object
- .size = 2 Byte
- .align = 1 => 2^1: 2 Byte
long k = 0#
Initialized global long k = 0;
is placed in the .bss
section:
- .type = %object: data object
- .size = 8 Byte
- .align = 3 => 2^3: 8 Byte
static int l#
Uninitialized local static int l;
:
At this momoent, l
is labelled as .comm
which means it's a tentative definition.
Tentative, Induced, External
Modern C | 13 Storage
Tentative indicates that a definition is implied only if there is no other definition with an initializer.
Induced indicates that the linkage is internal if another declaration with internal linkage has been met prior to that declaration;
otherwise, it is external.
The compiler will not allocate any space for l
yet. The two "4"s at the end mean that if space is allocated for l
in the future, the size of l
will be 4 bytes and the address of l
should be 4-byte aligned.
static short m = 5#
Initialized static short m = 5;
is placed in the .data
section:
- .type = %object: data object
- .size = 2 Byte
- .align = 1 => 2^1: 2 Byte
static long n = 0#
Uninitialized local static long n = 0;
:
Why initialized n
is also labelled as .comm
as uninitialized l
?
char *str1#
Initialized global char *str1 = "hello";
is placed in the .rodata
section, alias to label .LC0
:
- .align = 3 => 2^3: 8 Byte under aarch64/LP64
.global str1
.section .rodata
.align 3
.LC0:
.string "hello"
.section .data.rel.local,"aw"
.align 3
.type str1, %object
.size str1, 8
str1:
.xword .LC0
static char *str2#
Initialized static char *str2 = "world";
is also placed in the .rodata
section, alias to label .LC1
:
- .align = 3 => 2^3: 8 Byte under aarch64/LP64
.section .rodata
.align 3
.LC1:
.string "world"
.section .data.rel.local
.align 3
.type str2, %object
.size str2, 8
str2:
.xword .LC1
printf in func()#
format#
The format string of printf()
in func() are placed in the .rodata
section, labelled as .LC2
and .LC3
:
- .align = 3 => 2^3: 8 Byte under aarch64/LP64
.section .rodata
.align 3
.LC2:
.string "func global static: ijk = %ld\n"
.align 3
.LC3:
.string "func locals: r+s=%d, ia[3]=%d, ca=%s\n"
.align 3
.LC4:
.string "func local static: o++=%ld, ++p=%hd, q++=%d\n"
printf 1#
printf("func global static: ijk = %ld\n", i+j+k);
-
load
j
toW1
:- line 113~114: after
ADRP
andADD
,X0
holds address ofshort j
. - line 115: LDRSH(Load Register Signed Halfword) loads a halfword(16-bits) from address
X0
and sign extends the result to 32 bits, writes the result toW0
. - line 116: w1=w0,
W1
storesj
, vacateW0
for other use.
- line 113~114: after
-
load
i
toW0
:- line 117~118: after
ADRP
andADD
,X0
holds address ofint i
. - line 119: w0=[x0] =>
W0=i
;
- line 117~118: after
-
calc
j+i
and store toX1
:- line 120: w0=w1+w0 =>
W0=j+i
; - line 121: SXTW(Sign Extend Word)
W0
toX1
(64-bits), vacateW0
.
- line 120: w0=w1+w0 =>
-
load
k
toX0
:- line 123~124: after
ADRP
andADD
,X0
holds address oflong k
. - line 125: x0=[x0] =>
X0=k
;
- line 123~124: after
-
calc
(j+i)+k
and store toX1
:- line 126: x0=x1+x0 =>
X0=(j+i)+k
; - line 127: x1=x0,
X1
storesi+j+k
, vacateX0
.
- line 126: x0=x1+x0 =>
-
line 128~129:
X0
holds the format placeholder of printf --.LC2
.
printf 2#
Look at char ca[] = "hello";
, array name ca
will decays to a pointer.
ca
is a local auto variable on the stack. As its value is equal to global string str1
, it's optimized to point to the same copy. So it stores the address of str1
(alias to .LC0
).
printf("func locals: r+s=%d, ia[3]=%d, ca=%s\n", r+s, ia[3], ca);
- line 105~106, after
ADRP
andADD
,X1
holds address of label.LC0
- line 107: x0=sp+48, auto var in stack for
ca
(type of char *) - line 108: w2=[x1],
W2
holds address of.LC0
- line 109: [x0]=w2,
ca
holds the address of (points to).LC0
- line 110~111: ldrh w1, [x1, 4]; strh w1, [x0, 4]; ?
- line 136~137: x2=sp+48; x3=x2 =>
X3=sp+48
which storesca
. - line 140~141:
X0
holds the format placeholder of printf --.LC3
.
printf 3(o,p,q)#
Local static o
and q
are labelled as .comm
at the moment; Initialized p
is placed in the .data
section.
They're suffixed o.5
, p.4
, q.3
to avoid conflicting with same-named outer-scope vars.
// static long o;
.local o.5
.comm o.5,8,8
// static short p = 3;
.data
.align 1
.type p.4, %object
.size p.4, 2
p.4:
.hword 3
// static int q = 0;
.local q.3
.comm q.3,4,4
printf("func local static: o++=%ld, ++p=%hd, q++=%d\n", o++, ++p, q++);
-
o++
- line 144~145: x0=&o(load
o
) - line 146: x0=o.5
- line 147: x2=x0+1 => x2 = o.5+1(auto increment)
- line 148~149: x1=&o(reload
o
) - line 150: o.5=x2 => update o.5+1 to o.5
- line 178: x1=x0(X1 for printf
o++=%ld
) output originalo
- line 144~145: x0=&o(load
-
++p
- line 152~153: x1=&p(load
p
) - line 154: LDRSH(Load Register Signed Halfword) loads a halfword(16-bits) from address
X1
and sign extends the result to 32 bits, writes the result toW1
. - line 155: w1 = w1 & 0x0000ffff, clear high 16-bits, leave low 16-bits (represent short value).
- line 156: w1 = w1+1(auto increment)
- line 157: w1 = w1 & 0x0000ffff, clear high 16-bits, leave low 16-bits (represent short value).
- line 158: SXTH(Sign Extend Halfword)
W1
toW2
, vacateW1
. - line 160~161: x1=&p(reload
p
) - line 162: STRH(Store Register Halfword)
W2
to[X1]
=> update p.4+1 to p.4 - line 164~165: x1=&p(reload latest
p
) - line 166:
LDRSH
(Load Register Signed Halfword) loads a halfword(16-bits) from addressX1
and sign extends the result to 32 bits, writes the result toW1
. - line 168,177: w4=w1; w2=w4(W2 for printf
++p=%hd
)
- line 152~153: x1=&p(load
-
q++
(almost the same aso++
)- line 169~170: x1=&q(load
q
) - line 171: w1=q.3
- line 172: w3=w1+1 => w3 = q.3+1(auto increment)
- line 173~174: x2=&q(reload
q
) - line 175: q.3=w3 => update q.3+1 to q.3
- line 176: w3=w1(W3 for printf
q++=%d
) output originalq
- line 169~170: x1=&q(load
-
line 179~180:
X0
holds the format placeholder of printf --.LC4
.
printf in main()#
format#
The format string of printf()
in main() are also placed in the .rodata
section, labelled as .LC5
, .LC6
and .LC7
:
.section .rodata
.align 3
.LC5:
.string "lmn = %ld\n"
.align 3
.LC6:
.string "uvw = %ld\n"
.align 3
.LC7:
.string "strlen(%s) = %zu; strlen(%s) = %zu\n"
printf 1#
long lmn = l+m+n; printf("lmn = %ld\n", lmn);
- line 245~146: x0=&m(load
m
) - line 247:
LDRSH
(Load Register Signed Halfword) loads a halfword(16-bits) from addressX0
and sign extends the result to 32 bits, writes the result toW0
- line 248: w1=w0, vacate
W0
- line 249~250: x0=&l(load
l
) - line 251: w0=[x0]
- line 252: w0=w1+w0 => W0=m+l
- line 253:
SXTW
(Sign Extend Word)W0
toX1
(64-bits), vacateW0
. - line 255~256: x0=&n(load
n
) - line 257: x0=[x0]
- line 259: x0=x1+x0 => X0=(m+l)+n
- line 260: [sp+64]=x0 => store (m+l)+n to sp+64(auto var
lmn
in stack) - line 262: x1=[sp+64] => load
lmn
toX1
(for printflmn = %ld
) - line 263~264:
X0
holds the format placeholder of printf --.LC5
.
printf 2(u+v+w)#
Local static u
and w
are labelled as .comm
at the moment; Initialized v
is placed in the .data
section.
They're suffixed u.0
, v.1
, w.2
to avoid conflicting with same-named outer-scope vars.
.data
// static long w = 0;
.local w.2
.comm w.2,8,8
// static int v = 3;
.align 2
.type v.1, %object
.size v.1, 4
v.1:
.word 3
// static short u;
.local u.0
.comm u.0,2,2
long uvw = u+v+w; printf("uvw = %ld\n", uvw);
- line 267~268: x0=&u(load
u
) - line 269:
LDRSH
(Load Register Signed Halfword) loads a halfword(16-bits) from addressX0
and sign extends the result to 32 bits, writes the result toW0
- line 270: w1=w0, vacate
W0
- line 271~272: x0=&v(load
v
) - line 273: w0=[x0]
- line 274: w0=w1+w0 => W0=u+v
- line 275:
SXTW
(Sign Extend Word)W0
toX1
(64-bits), vacateW0
. - line 277~278: x0=&w(load
w
) - line 279: x0=[x0]
- line 281: x0=x1+x0 => X0=(u+v)+w
- line 282: [sp+72]=x0 => store (u+v)+w to sp+72(auto var
uvw
in stack) - line 284: x1=[sp+72] => load
uvw
toX1
(for printfuvw = %ld
) - line 285~286:
X0
holds the format placeholder of printf --.LC6
.
printf 3#
printf("strlen(%s) = %zu; strlen(%s) = %zu\n", str1, strlen(str1), str2, strlen(str2));
- line 289~290: x0=&str1(load
str1
) - line 291: x19=[x0]
- line 292~293: x0=&str1(reload
str1
) - line 294: x0=[x0] (param for strlen)
- line 295~296: x21=strlen(str1)
- line 297~298: x0=&str2(load
str2
) - line 299: x20=[x0]
- line 300~301: x0=&str2(reload
str2
) - line 302: x0=[x0] (param for strlen)
- line 303~304: x4=strlen(str2)
- line 305: x3=x20=&str2
- line 306: x2=x21=strlen(str1)
- line 307: x1=x19=&str1
- line 308~309:
X0
holds the format placeholder of printf --.LC7
.