Index: class-dump/src/CDObjectiveC1Processor.m |
=================================================================== |
--- class-dump/src/CDObjectiveC1Processor.m (revision 0) |
+++ class-dump/src/CDObjectiveC1Processor.m (revision 0) |
@@ -0,0 +1,637 @@ |
+// -*- mode: ObjC -*- |
+ |
+// This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files. |
+// Copyright (C) 1997-1998, 2000-2001, 2004-2010 Steve Nygard. |
+ |
+#import "CDObjectiveC1Processor.h" |
+ |
+#include <mach-o/arch.h> |
+ |
+#import "CDClassDump.h" |
+#import "CDLCDylib.h" |
+#import "CDMachOFile.h" |
+#import "CDOCCategory.h" |
+#import "CDOCClass.h" |
+#import "CDOCIvar.h" |
+#import "CDOCMethod.h" |
+#import "CDOCModule.h" |
+#import "CDOCProtocol.h" |
+#import "CDOCSymtab.h" |
+#import "CDSection32.h" |
+#import "CDLCSegment32.h" |
+#import "NSArray-Extensions.h" |
+#import "CDVisitor.h" |
+ |
+ |
+#import "CDSection.h" |
+#import "CDLCSegment.h" |
+ |
+// Section: __module_info |
+struct cd_objc_module { |
+ uint32_t version; |
+ uint32_t size; |
+ uint32_t name; |
+ uint32_t symtab; |
+}; |
+ |
+// Section: __symbols |
+struct cd_objc_symtab |
+{ |
+ uint32_t sel_ref_cnt; |
+ uint32_t refs; // not used until runtime? |
+ uint16_t cls_def_count; |
+ uint16_t cat_def_count; |
+ //long class_pointer; |
+}; |
+ |
+// Section: __class |
+struct cd_objc_class |
+{ |
+ uint32_t isa; |
+ uint32_t super_class; |
+ uint32_t name; |
+ uint32_t version; |
+ uint32_t info; |
+ uint32_t instance_size; |
+ uint32_t ivars; |
+ uint32_t methods; |
+ uint32_t cache; |
+ uint32_t protocols; |
+}; |
+ |
+// Section: ?? |
+struct cd_objc_category |
+{ |
+ uint32_t category_name; |
+ uint32_t class_name; |
+ uint32_t methods; |
+ uint32_t class_methods; |
+ uint32_t protocols; |
+}; |
+ |
+// Section: __instance_vars |
+struct cd_objc_ivar_list |
+{ |
+ uint32_t ivar_count; |
+ // Followed by ivars |
+}; |
+ |
+// Section: __instance_vars |
+struct cd_objc_ivar |
+{ |
+ uint32_t name; |
+ uint32_t type; |
+ uint32_t offset; |
+}; |
+ |
+// Section: __inst_meth |
+struct cd_objc_method_list |
+{ |
+ uint32_t _obsolete; |
+ uint32_t method_count; |
+ // Followed by methods |
+}; |
+ |
+// Section: __inst_meth |
+struct cd_objc_method |
+{ |
+ uint32_t name; |
+ uint32_t types; |
+ uint32_t imp; |
+}; |
+ |
+ |
+struct cd_objc_protocol_list |
+{ |
+ uint32_t next; |
+ uint32_t count; |
+ //uint32_t list; |
+}; |
+ |
+struct cd_objc_protocol |
+{ |
+ uint32_t isa; |
+ uint32_t protocol_name; |
+ uint32_t protocol_list; |
+ uint32_t instance_methods; |
+ uint32_t class_methods; |
+}; |
+ |
+struct cd_objc_protocol_method_list |
+{ |
+ uint32_t method_count; |
+ // Followed by methods |
+}; |
+ |
+struct cd_objc_protocol_method |
+{ |
+ uint32_t name; |
+ uint32_t types; |
+}; |
+ |
+static BOOL debug = NO; |
+ |
+@implementation CDObjectiveC1Processor |
+ |
+- (id)initWithMachOFile:(CDMachOFile *)aMachOFile; |
+{ |
+ if ([super initWithMachOFile:aMachOFile] == nil) |
+ return nil; |
+ |
+ modules = [[NSMutableArray alloc] init]; |
+ |
+ return self; |
+} |
+ |
+- (void)dealloc; |
+{ |
+ [modules release]; |
+ |
+ [super dealloc]; |
+} |
+ |
+- (void)process; |
+{ |
+ if ([machOFile isEncrypted] == NO && [machOFile canDecryptAllSegments]) { |
+ [super process]; |
+ |
+ [self processModules]; |
+ } |
+} |
+ |
+// |
+// Formerly private |
+// |
+ |
+- (void)processModules; |
+{ |
+ CDLCSegment *objcSegment; |
+ CDSection *moduleSection; |
+ NSData *sectionData; |
+ CDDataCursor *cursor; |
+ |
+ objcSegment = [machOFile segmentWithName:@"__OBJC"]; |
+ moduleSection = [objcSegment sectionWithName:@"__module_info"]; |
+ sectionData = [moduleSection data]; |
+ |
+ cursor = [[CDDataCursor alloc] initWithData:sectionData]; |
+ [cursor setByteOrder:[machOFile byteOrder]]; |
+ while ([cursor isAtEnd] == NO) { |
+ struct cd_objc_module objcModule; |
+ CDOCModule *module; |
+ NSString *name; |
+ NSArray *array; |
+ |
+ objcModule.version = [cursor readInt32]; |
+ objcModule.size = [cursor readInt32]; |
+ objcModule.name = [cursor readInt32]; |
+ objcModule.symtab = [cursor readInt32]; |
+ |
+ //NSLog(@"objcModule.size: %u", objcModule.size); |
+ //NSLog(@"sizeof(struct cd_objc_module): %u", sizeof(struct cd_objc_module)); |
+ assert(objcModule.size == sizeof(struct cd_objc_module)); // Because this is what we're assuming. |
+ |
+ name = [machOFile stringAtAddress:objcModule.name]; |
+ if (name != nil && [name length] > 0 && debug) |
+ NSLog(@"Note: a module name is set: %@", name); |
+ |
+ //NSLog(@"%08x %08x %08x %08x - '%@'", objcModule.version, objcModule.size, objcModule.name, objcModule.symtab, name); |
+ //NSLog(@"\tsect: %@", [[machOFile segmentContainingAddress:objcModule.name] sectionContainingAddress:objcModule.name]); |
+ //NSLog(@"symtab: %08x", objcModule.symtab); |
+ |
+ module = [[CDOCModule alloc] init]; |
+ [module setVersion:objcModule.version]; |
+ [module setName:[machOFile stringAtAddress:objcModule.name]]; |
+ [module setSymtab:[self processSymtabAtAddress:objcModule.symtab]]; |
+ [modules addObject:module]; |
+ |
+ array = [[module symtab] classes]; |
+ if (array != nil) |
+ [classes addObjectsFromArray:array]; |
+ |
+ array = [[module symtab] categories]; |
+ if (array != nil) |
+ [categories addObjectsFromArray:array]; |
+ |
+ [module release]; |
+ } |
+ |
+ [cursor release]; |
+} |
+ |
+- (CDOCSymtab *)processSymtabAtAddress:(uint32_t)address; |
+{ |
+ CDDataCursor *cursor; |
+ struct cd_objc_symtab objcSymtab; |
+ CDOCSymtab *aSymtab = nil; |
+ unsigned int index; |
+ |
+ //---------------------------------------- |
+ |
+ cursor = [[CDDataCursor alloc] initWithData:[machOFile data]]; |
+ [cursor setByteOrder:[machOFile byteOrder]]; |
+ [cursor setOffset:[machOFile dataOffsetForAddress:address segmentName:@"__OBJC"]]; |
+ //[cursor setOffset:[machOFile dataOffsetForAddress:address]]; |
+ //NSLog(@"cursor offset: %08x", [cursor offset]); |
+ if ([cursor offset] != 0) { |
+ objcSymtab.sel_ref_cnt = [cursor readInt32]; |
+ objcSymtab.refs = [cursor readInt32]; |
+ objcSymtab.cls_def_count = [cursor readInt16]; |
+ objcSymtab.cat_def_count = [cursor readInt16]; |
+ //NSLog(@"[@ %08x]: %08x %08x %04x %04x", address, objcSymtab.sel_ref_cnt, objcSymtab.refs, objcSymtab.cls_def_count, objcSymtab.cat_def_count); |
+ |
+ aSymtab = [[[CDOCSymtab alloc] init] autorelease]; |
+ |
+ for (index = 0; index < objcSymtab.cls_def_count; index++) { |
+ CDOCClass *aClass; |
+ uint32_t val; |
+ |
+ val = [cursor readInt32]; |
+ //NSLog(@"%4d: %08x", index, val); |
+ |
+ aClass = [self processClassDefinitionAtAddress:val]; |
+ if (aClass != nil) |
+ [aSymtab addClass:aClass]; |
+ } |
+ |
+ for (index = 0; index < objcSymtab.cat_def_count; index++) { |
+ CDOCCategory *aCategory; |
+ uint32_t val; |
+ |
+ val = [cursor readInt32]; |
+ //NSLog(@"%4d: %08x", index, val); |
+ |
+ aCategory = [self processCategoryDefinitionAtAddress:val]; |
+ if (aCategory != nil) |
+ [aSymtab addCategory:aCategory]; |
+ } |
+ } |
+ |
+ [cursor release]; |
+ |
+ return aSymtab; |
+} |
+ |
+- (CDOCClass *)processClassDefinitionAtAddress:(uint32_t)address; |
+{ |
+ CDDataCursor *cursor; |
+ struct cd_objc_class objcClass; |
+ CDOCClass *aClass; |
+ NSString *name; |
+ |
+ cursor = [[CDDataCursor alloc] initWithData:[machOFile data]]; |
+ [cursor setByteOrder:[machOFile byteOrder]]; |
+ [cursor setOffset:[machOFile dataOffsetForAddress:address]]; |
+ |
+ objcClass.isa = [cursor readInt32]; |
+ objcClass.super_class = [cursor readInt32]; |
+ objcClass.name = [cursor readInt32]; |
+ objcClass.version = [cursor readInt32]; |
+ objcClass.info = [cursor readInt32]; |
+ objcClass.instance_size = [cursor readInt32]; |
+ objcClass.ivars = [cursor readInt32]; |
+ objcClass.methods = [cursor readInt32]; |
+ objcClass.cache = [cursor readInt32]; |
+ objcClass.protocols = [cursor readInt32]; |
+ |
+ name = [machOFile stringAtAddress:objcClass.name]; |
+ //NSLog(@"name: %08x", objcClass.name); |
+ //NSLog(@"name = %@", name); |
+ if (name == nil) { |
+ NSLog(@"Note: objcClass.name was %08x, returning nil.", objcClass.name); |
+ [cursor release]; |
+ return nil; |
+ } |
+ |
+ aClass = [[[CDOCClass alloc] init] autorelease]; |
+ [aClass setName:name]; |
+ [aClass setSuperClassName:[machOFile stringAtAddress:objcClass.super_class]]; |
+ //NSLog(@"[aClass superClassName]: %@", [aClass superClassName]); |
+ |
+ // Process ivars |
+ if (objcClass.ivars != 0) { |
+ uint32_t count, index; |
+ NSMutableArray *ivars; |
+ |
+ [cursor setOffset:[machOFile dataOffsetForAddress:objcClass.ivars]]; |
+ NSParameterAssert([cursor offset] != 0); |
+ |
+ count = [cursor readInt32]; |
+ ivars = [[NSMutableArray alloc] init]; |
+ for (index = 0; index < count; index++) { |
+ struct cd_objc_ivar objcIvar; |
+ NSString *name, *type; |
+ |
+ objcIvar.name = [cursor readInt32]; |
+ objcIvar.type = [cursor readInt32]; |
+ objcIvar.offset = [cursor readInt32]; |
+ |
+ name = [machOFile stringAtAddress:objcIvar.name]; |
+ type = [machOFile stringAtAddress:objcIvar.type]; |
+ |
+ // bitfields don't need names. |
+ // NSIconRefBitmapImageRep in AppKit on 10.5 has a single-bit bitfield, plus an unnamed 31-bit field. |
+ if (type != nil) { |
+ CDOCIvar *anIvar; |
+ |
+ anIvar = [[CDOCIvar alloc] initWithName:name type:type offset:objcIvar.offset]; |
+ [ivars addObject:anIvar]; |
+ [anIvar release]; |
+ } |
+ } |
+ |
+ [aClass setIvars:[NSArray arrayWithArray:ivars]]; |
+ [ivars release]; |
+ } |
+ |
+ // Process instance methods |
+ for (CDOCMethod *method in [self processMethodsAtAddress:objcClass.methods]) |
+ [aClass addInstanceMethod:method]; |
+ |
+ // Process meta class |
+ { |
+ struct cd_objc_class metaClass; |
+ |
+ NSParameterAssert(objcClass.isa != 0); |
+ //NSLog(@"meta class, isa = %08x", objcClass.isa); |
+ |
+ [cursor setOffset:[machOFile dataOffsetForAddress:objcClass.isa]]; |
+ |
+ metaClass.isa = [cursor readInt32]; |
+ metaClass.super_class = [cursor readInt32]; |
+ metaClass.name = [cursor readInt32]; |
+ metaClass.version = [cursor readInt32]; |
+ metaClass.info = [cursor readInt32]; |
+ metaClass.instance_size = [cursor readInt32]; |
+ metaClass.ivars = [cursor readInt32]; |
+ metaClass.methods = [cursor readInt32]; |
+ metaClass.cache = [cursor readInt32]; |
+ metaClass.protocols = [cursor readInt32]; |
+ |
+#if 0 |
+ // TODO (2009-06-23): See if there's anything else interesting here. |
+ NSLog(@"metaclass= isa:%08x super:%08x name:%08x ver:%08x info:%08x isize:%08x ivar:%08x meth:%08x cache:%08x proto:%08x", |
+ metaClass.isa, metaClass.super_class, metaClass.name, metaClass.version, metaClass.info, metaClass.instance_size, |
+ metaClass.ivars, metaClass.methods, metaClass.cache, metaClass.protocols); |
+#endif |
+ // Process class methods |
+ for (CDOCMethod *method in [self processMethodsAtAddress:metaClass.methods]) |
+ [aClass addClassMethod:method]; |
+ } |
+ |
+ // Process protocols |
+ for (CDOCProtocol *protocol in [self uniquedProtocolListAtAddress:objcClass.protocols]) |
+ [aClass addProtocol:protocol]; |
+ |
+ [cursor release]; |
+ |
+ return aClass; |
+} |
+ |
+// Returns list of uniqued protocols. |
+- (NSArray *)uniquedProtocolListAtAddress:(uint32_t)address; |
+{ |
+ NSMutableArray *protocols; |
+ |
+ protocols = [[[NSMutableArray alloc] init] autorelease];; |
+ |
+ if (address != 0) { |
+ CDDataCursor *cursor; |
+ struct cd_objc_protocol_list protocolList; |
+ uint32_t index; |
+ |
+ cursor = [[CDDataCursor alloc] initWithData:[machOFile data]]; |
+ [cursor setByteOrder:[machOFile byteOrder]]; |
+ [cursor setOffset:[machOFile dataOffsetForAddress:address]]; |
+ |
+ protocolList.next = [cursor readInt32]; |
+ protocolList.count = [cursor readInt32]; |
+ |
+ for (index = 0; index < protocolList.count; index++) { |
+ uint32_t val; |
+ CDOCProtocol *protocol, *uniqueProtocol; |
+ |
+ val = [cursor readInt32]; |
+ protocol = [protocolsByAddress objectForKey:[NSNumber numberWithUnsignedInt:val]]; |
+ //NSLog(@"%3d protocol @ %08x: %@", index, val, [protocol name]); |
+ if (protocol != nil) { |
+ uniqueProtocol = [protocolsByName objectForKey:[protocol name]]; |
+ if (uniqueProtocol != nil) |
+ [protocols addObject:uniqueProtocol]; |
+ } |
+ } |
+ |
+ [cursor release]; |
+ } |
+ |
+ return protocols; |
+} |
+ |
+- (NSArray *)processMethodsAtAddress:(uint32_t)address; |
+{ |
+ return [self processMethodsAtAddress:address isFromProtocolDefinition:NO]; |
+} |
+ |
+- (NSArray *)processMethodsAtAddress:(uint32_t)address isFromProtocolDefinition:(BOOL)isFromProtocolDefinition; |
+{ |
+ CDDataCursor *cursor; |
+ NSMutableArray *methods; |
+ |
+ if (address == 0) |
+ return [NSArray array]; |
+ |
+ methods = [NSMutableArray array]; |
+ |
+ cursor = [[CDDataCursor alloc] initWithData:[machOFile data]]; |
+ [cursor setByteOrder:[machOFile byteOrder]]; |
+ [cursor setOffset:[machOFile dataOffsetForAddress:address]]; |
+ if ([cursor offset] != 0) { |
+ struct cd_objc_method_list methodList; |
+ uint32_t index; |
+ |
+ if (isFromProtocolDefinition) |
+ methodList._obsolete = 0; |
+ else |
+ methodList._obsolete = [cursor readInt32]; |
+ methodList.method_count = [cursor readInt32]; |
+ |
+ for (index = 0; index < methodList.method_count; index++) { |
+ struct cd_objc_method objcMethod; |
+ NSString *name, *type; |
+ |
+ objcMethod.name = [cursor readInt32]; |
+ objcMethod.types = [cursor readInt32]; |
+ if (isFromProtocolDefinition) |
+ objcMethod.imp = 0; |
+ else |
+ objcMethod.imp = [cursor readInt32]; |
+ |
+ name = [machOFile stringAtAddress:objcMethod.name]; |
+ type = [machOFile stringAtAddress:objcMethod.types]; |
+ if (name != nil && type != nil) { |
+ CDOCMethod *method; |
+ |
+ method = [[CDOCMethod alloc] initWithName:name type:type imp:objcMethod.imp]; |
+ [methods addObject:method]; |
+ [method release]; |
+ } else { |
+ if (name == nil) NSLog(@"Note: Method name was nil (%08x, %p)", objcMethod.name, name); |
+ if (type == nil) NSLog(@"Note: Method type was nil (%08x, %p)", objcMethod.types, type); |
+ } |
+ } |
+ } |
+ |
+ [cursor release]; |
+ |
+ return [methods reversedArray]; |
+} |
+ |
+- (CDOCCategory *)processCategoryDefinitionAtAddress:(uint32_t)address; |
+{ |
+ CDOCCategory *aCategory = nil; |
+ |
+ if (address != 0) { |
+ CDDataCursor *cursor; |
+ struct cd_objc_category objcCategory; |
+ NSString *name; |
+ |
+ cursor = [[CDDataCursor alloc] initWithData:[machOFile data]]; |
+ [cursor setByteOrder:[machOFile byteOrder]]; |
+ [cursor setOffset:[machOFile dataOffsetForAddress:address]]; |
+ |
+ objcCategory.category_name = [cursor readInt32]; |
+ objcCategory.class_name = [cursor readInt32]; |
+ objcCategory.methods = [cursor readInt32]; |
+ objcCategory.class_methods = [cursor readInt32]; |
+ objcCategory.protocols = [cursor readInt32]; |
+ |
+ name = [machOFile stringAtAddress:objcCategory.category_name]; |
+ if (name == nil) { |
+ NSLog(@"Note: objcCategory.category_name was %08x, returning nil.", objcCategory.category_name); |
+ [cursor release]; |
+ return nil; |
+ } |
+ |
+ aCategory = [[[CDOCCategory alloc] init] autorelease]; |
+ [aCategory setName:name]; |
+ [aCategory setClassName:[machOFile stringAtAddress:objcCategory.class_name]]; |
+ |
+ for (CDOCMethod *method in [self processMethodsAtAddress:objcCategory.methods]) |
+ [aCategory addInstanceMethod:method]; |
+ |
+ for (CDOCMethod *method in [self processMethodsAtAddress:objcCategory.class_methods]) |
+ [aCategory addClassMethod:method]; |
+ |
+ for (CDOCProtocol *protocol in [self uniquedProtocolListAtAddress:objcCategory.protocols]) |
+ [aCategory addProtocol:protocol]; |
+ |
+ [cursor release]; |
+ } |
+ |
+ return aCategory; |
+} |
+ |
+- (CDOCProtocol *)protocolAtAddress:(uint32_t)address; |
+{ |
+ NSNumber *key; |
+ CDOCProtocol *aProtocol; |
+ |
+ key = [NSNumber numberWithUnsignedInt:address]; |
+ aProtocol = [protocolsByAddress objectForKey:key]; |
+ if (aProtocol == nil) { |
+ CDDataCursor *cursor; |
+ uint32_t v1, v2, v3, v4, v5; |
+ NSString *name; |
+ |
+ //NSLog(@"Creating new protocol from address: 0x%08x", address); |
+ aProtocol = [[[CDOCProtocol alloc] init] autorelease]; |
+ [protocolsByAddress setObject:aProtocol forKey:key]; |
+ |
+ cursor = [[CDDataCursor alloc] initWithData:[machOFile data]]; |
+ [cursor setByteOrder:[machOFile byteOrder]]; |
+ [cursor setOffset:[machOFile dataOffsetForAddress:address]]; |
+ |
+ v1 = [cursor readInt32]; |
+ v2 = [cursor readInt32]; |
+ v3 = [cursor readInt32]; |
+ v4 = [cursor readInt32]; |
+ v5 = [cursor readInt32]; |
+ name = [machOFile stringAtAddress:v2]; |
+ [aProtocol setName:name]; // Need to set name before adding to another protocol |
+ //NSLog(@"data offset for %08x: %08x", v2, [machOFile dataOffsetForAddress:v2]); |
+ //NSLog(@"[@ %08x] v1-5: 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x (%@)", address, v1, v2, v3, v4, v5, name); |
+ |
+ { |
+ uint32_t count, index; |
+ |
+ // Protocols |
+ if (v3 != 0) { |
+ uint32_t val; |
+ |
+ [cursor setOffset:[machOFile dataOffsetForAddress:v3]]; |
+ val = [cursor readInt32]; |
+ NSParameterAssert(val == 0); // next pointer, let me know if it's ever not zero |
+ //NSLog(@"val: 0x%08x", val); |
+ count = [cursor readInt32]; |
+ //NSLog(@"protocol count: %08x", count); |
+ for (index = 0; index < count; index++) { |
+ CDOCProtocol *anotherProtocol; |
+ |
+ val = [cursor readInt32]; |
+ //NSLog(@"val[%2d]: 0x%08x", index, val); |
+ anotherProtocol = [self protocolAtAddress:val]; |
+ if (anotherProtocol != nil) { |
+ [aProtocol addProtocol:anotherProtocol]; |
+ } else { |
+ NSLog(@"Note: another protocol was nil."); |
+ } |
+ } |
+ } |
+ |
+ // Instance methods |
+ for (CDOCMethod *method in [self processMethodsAtAddress:v4 isFromProtocolDefinition:YES]) |
+ [aProtocol addInstanceMethod:method]; |
+ |
+ // Class methods |
+ for (CDOCMethod *method in [self processMethodsAtAddress:v5 isFromProtocolDefinition:YES]) |
+ [aProtocol addClassMethod:method]; |
+ } |
+ |
+ [cursor release]; |
+ } else { |
+ //NSLog(@"Found existing protocol at address: 0x%08x", address); |
+ } |
+ |
+ return aProtocol; |
+} |
+ |
+// Protocols can reference other protocols, so we can't try to create them |
+// in order. Instead we create them lazily and just make sure we reference |
+// all available protocols. |
+ |
+// Many of the protocol structures share the same name, but have differnt method lists. Create them all, then merge/unique by name after. |
+// Perhaps a bit more work than necessary, but at least I can see exactly what is happening. |
+- (void)loadProtocols; |
+{ |
+ CDLCSegment *objcSegment; |
+ CDSection *protocolSection; |
+ uint32_t addr; |
+ int count, index; |
+ |
+ objcSegment = [machOFile segmentWithName:@"__OBJC"]; |
+ protocolSection = [objcSegment sectionWithName:@"__protocol"]; |
+ addr = [protocolSection addr]; |
+ |
+ count = [protocolSection size] / sizeof(struct cd_objc_protocol); |
+ for (index = 0; index < count; index++, addr += sizeof(struct cd_objc_protocol)) |
+ [self protocolAtAddress:addr]; // Forces them to be loaded |
+ |
+ [self createUniquedProtocols]; |
+} |
+ |
+- (NSData *)objcImageInfoData; |
+{ |
+ return [[[machOFile segmentWithName:@"__OBJC"] sectionWithName:@"__image_info"] data]; |
+} |
+ |
+@end |
Property changes on: class-dump/src/CDObjectiveC1Processor.m |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |