Clang-generated code violates x86_64 varargs ABI
| Originator: | jalkut | ||
| Number: | rdar://12151446 | Date Originated: | 22-Aug-2012 01:32 PM |
| Status: | Open | Resolved: | |
| Product: | Developer Tools | Product Version: | 4.4.1 |
| Classification: | Serious Bug | Reproducible: | Always |
22-Aug-2012 01:32 PM Daniel Jalkut:
Tracking down a crash report from a customer on 10.6.8, I traced it, with the help of friends Mark Dalrymple and Mike Ash, to a compiler behavior with clang, having to do with its treatment of the $rax register, specifically %al, before calling through to a varargs method.
From http://www.x86-64.org/documentation/abi.pdf:
"For calls that may call functions that use varargs or stdargs (prototype-less calls or calls to functions containing ellipsis (. . . ) in the declaration) %al16 is used as hidden argument to specify the number of vector registers used."
The attached source file "PunkItUp.m" contains a succinct example of code that, when compiled with -Os optimization using clang, presents a scenario where -[NSString stringWithFormat:] is called without initializing %al to 0.
As it happens, the failure to do this causes a predictable crash on 10.6.8, where apparently the implementation of stringWithFormat: makes unfortunate use of this uninitialized value.
I was able to confirm that breaking in the debugger on 10.6.8, just before the msgSend, and setting $rax to 0 before continuing with the method call does work around the crash.
I confirmed this bug still exists in clang built from SVN as of today's revision #162362.
22-Aug-2012 01:32 PM Daniel Jalkut:
'PunkItUp.m' was successfully uploaded
[PunkItUp.m source pasted below]
#import <Foundation/Foundation.h>
//
// This code was collaboratively written by Daniel Jalkut and Mark Dalrymple.
// The root cause of the bug was opined by Mike Ash.
//
// Demonstrates an x86_64 ABI violation wherein a method that calls the
// vararg -[NSString stringWithFormat:] does not specify as ZERO the number
// of vector registers being used.
//
// Runs fine everywhere:
// clang -g -Wall -framework Foundation -o PunkItUp PunkItUp.m
//
// Crashes on 10.6.8, runs on 10.7+:
// clang -Os -Wall -framework Foundation -o PunkItUp PunkItUp.m
//
// The source of this particular crash seems to be peculiar to the implementation
// of stringWithFormat: on 10.6.8, but the problematic uninitialization of %al could
// have implications on other frameworks in 10.7+.
//
@interface PunkItUp : NSObject
@end
@implementation PunkItUp
- (NSString*) formattedTimeStringForSeconds:(NSTimeInterval)timeInSeconds
{
NSUInteger integralTime = (NSUInteger)timeInSeconds;
NSUInteger HOURS = 3600;
NSUInteger MINUTES = 60;
NSUInteger hours = (integralTime / HOURS);
NSUInteger minutes = (integralTime % HOURS) / MINUTES;
NSUInteger seconds = (integralTime % HOURS) % MINUTES;
return [NSString stringWithFormat:@"%lu:%lu:%lu", hours, minutes, seconds];
}
- (id) init
{
if (self = [super init])
{
NSTimeInterval ook = 1.0;
NSString *blah = [self formattedTimeStringForSeconds: ook];
NSLog (@"blah: %@", blah);
}
return self;
}
@end
int main (void)
{
[[NSAutoreleasePool alloc] init];
(void) [[PunkItUp alloc] init];
return 0;
}
Comments
Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!
Note that this is NOT a 10.6 bug. It is a code generation bug in clang, that happens to show up by making 10.6 framework code crash. It's still a bug on 10.8, just less visible.
Being that 10.6 is as old as it is, it is unlikely they'll fix it. I have plenty of 10.3 bugs caused by an updated toolchain that were marked as wontfix in the 10.4 lifespan.
moral of the story, 10.6 is deprecated, don't target it.
Relationship to gcc-generated code as the varargs receiver function...
Mike Ash did some more research and discovered the "perfect storm" for this particular crash seems to be the gcc-generated varargs receiver code that generates jumps based on $rax, that is likely to exist in 10.6.8 frameworks, combined with the clang-generated caller code that neglects to initialize the register value.
Symptoms on 10.6.8
If you run into this on 10.6.8 you'll probably have some framework code run off into the weeds because of the bogus value in $rax. For me, it was this stringWithFormat: call that ends up jumping to a bogus $rcx location:
0x7fff84af1358 <+[NSString stringWithFormat:]>: push %rbp 0x7fff84af1359 <+[NSString stringWithFormat:]+1>: mov %rsp,%rbp 0x7fff84af135c <+[NSString stringWithFormat:]+4>: push %r12 0x7fff84af135e <+[NSString stringWithFormat:]+6>: push %rbx 0x7fff84af135f <+[NSString stringWithFormat:]+7>: sub $0xd0,%rsp 0x7fff84af1366 <+[NSString stringWithFormat:]+14>: mov %rcx,-0xa8(%rbp) 0x7fff84af136d <+[NSString stringWithFormat:]+21>: mov %r8,-0xa0(%rbp) 0x7fff84af1374 <+[NSString stringWithFormat:]+28>: mov %r9,-0x98(%rbp) 0x7fff84af137b <+[NSString stringWithFormat:]+35>: movzbl %al,%ecx 0x7fff84af137e <+[NSString stringWithFormat:]+38>: lea 0x0(,%rcx,4),%rax 0x7fff84af1386 <+[NSString stringWithFormat:]+46>: lea 0x29(%rip),%rcx # 0x7fff84af13b6 <+[NSString stringWithFormat:]+94> 0x7fff84af138d <+[NSString stringWithFormat:]+53>: sub %rax,%rcx 0x7fff84af1390 <+[NSString stringWithFormat:]+56>: lea -0x11(%rbp),%rax 0x7fff84af1394 <+[NSString stringWithFormat:]+60>: jmpq *%rcx Jumps to -> UNHAPPY CODE that will probably crash.
(Thanks Mike Ash for the further analysis on this).