Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-7991] [Windows] Dynamic casts cause the executable to crash #50524

Open
swift-ci opened this issue Jun 13, 2018 · 6 comments
Open

[SR-7991] [Windows] Dynamic casts cause the executable to crash #50524

swift-ci opened this issue Jun 13, 2018 · 6 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-7991
Radar None
Original Reporter Planitzer (JIRA User)
Type Bug
Environment

OS: Windows 10

SDK: Windows SDK Version 10.0.17134.0

Visual Studio 2017: v15.7.3

Swift: Swift version 4.2-dev (LLVM 58850e66ae, Clang 8c059b98e4, Swift a22b360)

Swift compiler compiled with a debug version of llvm-clang

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee Planitzer (JIRA)
Priority Medium

md5: a1f3a1f6115e344c8d439ade14da5dce

Issue Description:

Dynamic Swift casts like for example

if let foo = myStructInstance as? CustomStringConvertible {
   ...
}

will crash a Swift program. The simplest way to replicate this is to compile and run this Swift code:

print("Hello World")

This crashes when the print() function tries to dynamically cast the string to a TextOutputStreamable here:

sodlib/core/OutputStream:392

internal func _print_unlocked<T, TargetStream : TextOutputStream>(
  _ value: T, _ target: inout TargetStream) {

  ...
  
  if case let streamableObject as TextOutputStreamable = value { <-- here
    streamableObject.write(to: &target)
    return
  }

More specifically the exact crash location is in this code:

stdlib/runtime/ProtocolConformance.cpp:620

static const WitnessTable *
swift_conformsToProtocolImpl(const Metadata * const type,
                             const ProtocolDescriptor *protocol) {

   ...
    } else if (descriptor.getTypeKind()
                   == TypeMetadataRecordKind::DirectNominalTypeDescriptor ||
                 descriptor.getTypeKind()
                  == TypeMetadataRecordKind::IndirectNominalTypeDescriptor) {
        auto R = descriptor.getTypeContextDescriptor();  <--- here

because the section which contains the protocol conformance records appears to be incorrectly generated. Here is a dump of the protocol conformance section from swiftCore.dll (which is affected by the same problem):

Dump of file swiftCore.dll

File Type: DLL

SECTION HEADER #C
.sw5prtc name
    1489 virtual size
  8B4000 virtual address (00000001808B4000 to 00000001808B5488)
    1600 size of raw data
  887E00 file pointer to raw data (00887E00 to 008893FF)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         Read Only

RAW DATA #C
  00000001808B4000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B40A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B40B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B40C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B40D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B40E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B40F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808B4100: 00 00 00 00 00 00 00 00 00 00 00 00 1C F7 E1 FF  .............÷áÿ
  00000001808B4110: 50 F8 E1 FF EC F8 E1 FF C0 F9 E1 FF EC F9 E1 FF  PøáÿìøáÿÀùáÿìùáÿ
  00000001808B4120: 00 FA E1 FF FC FA E1 FF 00 FC E1 FF 8C FD E1 FF  .úáÿüúáÿ.üáÿ.ýáÿ
  00000001808B4130: 60 FE E1 FF 4C FF E1 FF 70 FF E1 FF 84 00 E2 FF  `þáÿLÿáÿpÿáÿ..âÿ
  00000001808B4140: C0 01 E2 FF DC 01 E2 FF F8 01 E2 FF 94 C3 E4 FF  À.âÿÜ.âÿø.âÿ.Ãäÿ

Notice the 0 bytes at the beginning of the section which as far as I can tell from reading the Swift sources shouldn't be there because the relative pointers for protocol conformance records are supposed to be never NULL. However those 0 bytes cause the Swift runtime to misinterpret the data and eventually it will try to parse one of the relative pointers as the type field of a protocol which then leads the swift_conformsToProtocolImpl() function right off a 1000 meter cliff and to a painful death.

Even the reflection strings section has those weird leading 0 bytes in there:

Dump of file swiftCore.dll

File Type: DLL

SECTION HEADER #&#8203;8
.sw5rfst name
    1679 virtual size
  8A6000 virtual address (00000001808A6000 to 00000001808A7678)
    1800 size of raw data
  87C800 file pointer to raw data (0087C800 to 0087DFFF)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         Read Only

RAW DATA #&#8203;8
  00000001808A6000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6110: 5F 62 61 73 65 00 5F 63 6F 75 6E 74 00 5F 73 74  _base._count._st
  00000001808A6120: 6F 72 61 67 65 00 5F 62 75 66 66 65 72 00 6E 65  orage._buffer.ne
  00000001808A6130: 77 56 61 6C 75 65 73 00 5F 76 61 6C 75 65 00 76  wValues._value.v
  00000001808A6140: 61 6C 75 65 00 72 61 77 56 61 6C 75 65 00 73 6D  alue.rawValue.sm

I've cross-checked with macOS and Linux and I don't see any leading 0 bytes on those systems.

The number of leading 0 bytes appears to be variable. Here is the reflection strings section from a simple Swift app that defines a single struct with two properties:

Dump of file hello.exe

File Type: EXECUTABLE IMAGE

SECTION HEADER #&#8203;9
.sw5rfst name
      18 virtual size
   22000 virtual address (0000000140022000 to 0000000140022017)
     200 size of raw data
   1AA00 file pointer to raw data (0001AA00 to 0001ABFF)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         Read Only

RAW DATA #&#8203;9
  0000000140022000: 00 00 00 00 00 00 00 00 62 61 72 00 66 6F 6F 00  ........bar.foo.
  0000000140022010: 00 00 00 00 00 00 00 00                          ........
@swift-ci
Copy link
Collaborator Author

Comment by Dietmar Planitzer (JIRA)

I ran the Swift compiler in the MSVC debugger so that I could look at the .obj file that the Swift compiler generates. The Swift metadata sections look good there. Here's the reflection strings section:

File Type: COFF OBJECT

SECTION HEADER #&#8203;7
   /1346 name (.sw5rfst$B)
       0 physical address
       0 virtual address
       8 size of raw data
     9F0 file pointer to raw data (000009F0 to 000009F7)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40100040 flags
         Initialized Data
         1 byte align
         Read Only

RAW DATA #&#8203;7
  00000000: 62 61 72 00 66 6F 6F 00                          bar.foo.

and the protocol conformance section:

File Type: COFF OBJECT

SECTION HEADER #&#8203;9
   /1390 name (.sw5prtc$B)
       0 physical address
       0 virtual address
       4 size of raw data
     A54 file pointer to raw data (00000A54 to 00000A57)
     A58 file pointer to relocation table
       0 file pointer to line numbers
       1 number of relocations
       0 number of line numbers
40300040 flags
         Initialized Data
         4 byte align
         Read Only

RAW DATA #&#8203;9
  00000000: 04 00 00 00                                      ....

So it looks like as if something goes wrong when the MSVC linker kicks in and generates the final exe.

Linker version:

Microsoft (R) Incremental Linker Version 14.14.26430.0

@belkadan
Copy link
Contributor

cc @compnerd

@swift-ci
Copy link
Collaborator Author

Comment by Dietmar Planitzer (JIRA)

Some more observations:

Looking at the stdlib/public/runtime/SwiftRT-COFF.cpp it appears that 8 zero bytes at the beginning and end of the sw5* sections are expected:

#define DECLARE_SWIFT_SECTION(name)                                            \
  PRAGMA(section("." #name "$A", long, read))                                  \
  __declspec(allocate("." #name "$A"))                                         \
  static uintptr_t __start_##name = 0;                                         \
                                                                               \
  PRAGMA(section("." #name "$C", long, read))                                  \
  __declspec(allocate("." #name "$C"))                                         \
  static uintptr_t __stop_##name = 0;

I do see those 8 zero bytes in the executable that I've built (the simple print("hello world"). But I see way more than 8 zero bytes in the swiftCore.dll and this is why we get the crash.

I've changed the definition of the _start##name and _stop#name variables in my local copy so that they store the value 0xDEADBEEF. This is what I see if I rebuild swiftCore.dll and run dumpbin on the sw5rfst section:

File Type: DLL

SECTION HEADER #&#8203;8
.sw5rfst name
    1679 virtual size
  8A6000 virtual address (00000001808A6000 to 00000001808A7678)
    1800 size of raw data
  87C800 file pointer to raw data (0087C800 to 0087DFFF)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         Read Only

RAW DATA #&#8203;8
  00000001808A6000: AF BE AD DE 00 00 00 00 00 00 00 00 00 00 00 00  ¯¾­Þ............
  00000001808A6010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A60F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A6110: 5F 62 61 73 65 00 5F 63 6F 75 6E 74 00 5F 73 74  _base._count._st
  ...
  00000001808A7560: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7570: AD DE AF BE 00 00 00 00 00 00 00 00 00 00 00 00  ­Þ¯¾............
  00000001808A7580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7590: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A75A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A75B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A75C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A75D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A75E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A75F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7660: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  00000001808A7670: 00 00 00 00 00 00 00 00 00                       .........

  Summary

        2000 .sw5rfst

So this shows the sw5rfst section after it has been created by merging the sw5rfst$A, sw5rfst$B and sw5rfstr$C input sections. Notice that what was the sw5rfst$A and the sw5rfst$C sections are now followed by 256 0 bytes. It's as if the linker has added 256 extra padding bytes at the end of those guys before it merged everything together. The first 8 bytes are my 0xDEADBEEF and I think that the next 8 0 bytes are there because the sw5rfst$B section declares in its section header that it wants to be 16 byte aligned.

I found this stack overflow discussion which mentions that sections may receive 256 byte padding. The last answer there suggests that the padding is only added for debug builds. And indeed swiftCore.dll contains .debug sections so I guess it was built in debug mode.

Now assuming that it's indeed the debug mode that causes the insertion of the extra padding bytes, it's not really clear to me what the appropriate fix would be. Whether the Swift runtime needs to be taught to somehow figure out whether the extra padding is there and ignore it or whether it's possible and desirable to build / link debug versions of swiftCore.dll such that it never contains extra padding bytes (I guess that this would be the preferable thing to do).

@swift-ci
Copy link
Collaborator Author

Comment by Dietmar Planitzer (JIRA)

There's also this interesting comment

// The MSVC incremental linker may pad globals out to 256 bytes. As long...

in this clang / compiler-rt file. So I guess I'll rebuild everything with /INCREMENTAL:NO and hope that this fixes it.

@swift-ci
Copy link
Collaborator Author

Comment by Dietmar Planitzer (JIRA)

Making sure that all Swift libraries are linked with /INCREMENTAL:NO fixes this.

However it would probably make sense to change the Swift runtime implementation such that it is able to deal with incrementally linked libraries. But the caveat here is that while the beginning of the sw5*$B section is always at alignTo(sizeof(_start_sw5*) + 256, 16), the location of the corresponding __stop_sw5* variable can be much further out than 256 bytes...

@swift-ci
Copy link
Collaborator Author

Comment by Dietmar Planitzer (JIRA)

Created a PR with a fix for this: Implemented runtime support for MSVC incremental linking.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

2 participants