ruby/win32/mkexports.rb
Pierrick Bouvier 784fdecc4c
windows-arm64 support (#8995)
* [win32] fix compilation for windows-arm64

Credits to MSYS2 Ruby package using this patch.

* [win32] nm use full options

Fix compilation error when using MSYS2 environment.
Credits to MSYS2 Ruby package using this patch.

* [win32] detect llvm-windres (used for windows-arm64)

When adding preprocessor option for llvm-windres (using clang as
parameter), it fails. Thus, do not add this.

It's needed to be able to compile windows-arm64 version, because MSYS2
toolchain is LLVM based (instead of GCC/binutils).

* [win32] pioinfo detection for windows-arm64

This fixes "unexpected ucrtbase.dll" for native windows-arm64 ruby
binary. It does not solve issue with x64 version emulated on this
platform.

Value of pioinfo pointer can be found in ucrtbase.dll at latest adrp/add
sequence before return of _isatty function. This works for both release
and debug ucrt.

Due to the nature of aarch64 ISA (vs x86 or x64), it's needed to
disassemble instructions to retrieve offset value, which is a bit more
complicated than matching specific string patterns.

Details about adrp/add usage can be found in this blog post:
https://devblogs.microsoft.com/oldnewthing/20220809-00/?p=106955
For instruction decoding, the Arm documentation was used as a reference.
2023-11-23 17:17:28 +09:00

182 lines
4.5 KiB
Ruby
Executable file

#!./miniruby -sI.
$name = $library = $description = nil
module RbConfig
autoload :CONFIG, "./rbconfig"
end
class Exports
PrivateNames = /(?:Init_|InitVM_|ruby_static_id_|threadptr|_ec_|DllMain\b)/
def self.create(*args, &block)
platform = RUBY_PLATFORM
klass = constants.find do |p|
break const_get(p) if platform.include?(p.to_s.downcase)
end
unless klass
raise ArgumentError, "unsupported platform: #{platform}"
end
klass.new(*args, &block)
end
def self.extract(objs, *rest)
create(objs).exports(*rest)
end
def self.output(output = $output, &block)
if output
File.open(output, 'wb', &block)
else
yield STDOUT
end
end
def initialize(objs)
syms = {}
winapis = {}
syms["ruby_sysinit_real"] = "ruby_sysinit"
each_export(objs) do |internal, export|
syms[internal] = export
winapis[$1] = internal if /^_?(rb_w32_\w+)(?:@\d+)?$/ =~ internal
end
incdir = File.join(File.dirname(File.dirname(__FILE__)), "include/ruby")
read_substitution(incdir+"/win32.h", syms, winapis)
read_substitution(incdir+"/subst.h", syms, winapis)
syms["rb_w32_vsnprintf"] ||= "ruby_vsnprintf"
syms["rb_w32_snprintf"] ||= "ruby_snprintf"
@syms = syms
end
def read_substitution(header, syms, winapis)
File.foreach(header) do |line|
if /^#define (\w+)\((.*?)\)\s+(?:\(void\))?(rb_w32_\w+)\((.*?)\)\s*$/ =~ line and
$2.delete(" ") == $4.delete(" ")
export, internal = $1, $3
if syms[internal] or internal = winapis[internal]
syms[forwarding(internal, export)] = internal
end
end
end
end
def exports(name = $name, library = $library, description = $description)
exports = []
if name
exports << "Name " + name
elsif library
exports << "Library " + library
end
exports << "Description " + description.dump if description
exports << "VERSION #{RbConfig::CONFIG['MAJOR']}.#{RbConfig::CONFIG['MINOR']}"
exports << "EXPORTS" << symbols()
exports
end
private
def forwarding(internal, export)
internal.sub(/^[^@]+/, "\\1#{export}")
end
def each_export(objs)
end
def objdump(objs, &block)
if objs.empty?
$stdin.each_line(&block)
else
each_line(objs, &block)
end
end
def symbols()
@syms.sort.collect {|k, v| v ? v == true ? "#{k} DATA" : "#{k}=#{v}" : k}
end
end
class Exports::Mswin < Exports
def each_line(objs, &block)
IO.popen(%w"dumpbin -symbols -exports" + objs) do |f|
f.each(&block)
end
end
def each_export(objs)
noprefix = ($arch ||= nil and /^(sh|i\d86)/ !~ $arch)
objs = objs.collect {|s| s.tr('/', '\\')}
filetype = nil
objdump(objs) do |l|
if filetype
if /^\f/ =~ l
filetype = nil
next
end
case filetype
when /OBJECT/, /LIBRARY/
l.chomp!
next if /^[[:xdigit:]]+ 0+ UNDEF / =~ l
next unless /External/ =~ l
next if /(?:_local_stdio_printf_options|v(f|sn?)printf(_s)?_l)\Z/ =~ l
next unless l.sub!(/.*?\s(\(\)\s+)?External\s+\|\s+/, '')
is_data = !$1
if noprefix or /^[@_]/ =~ l
next if /(?!^)@.*@/ =~ l || /@[[:xdigit:]]{8,32}$/ =~ l ||
/^_?#{PrivateNames}/o =~ l
l.sub!(/^[@_]/, '') if /@\d+$/ !~ l
elsif !l.sub!(/^(\S+) \([^@?\`\']*\)$/, '\1')
next
end
when /DLL/
next unless l.sub!(/^\s*\d+\s+[[:xdigit:]]+\s+[[:xdigit:]]+\s+/, '')
else
next
end
yield l.strip, is_data
else
filetype = l[/^File Type: (.+)/, 1]
end
end
yield "strcasecmp", "msvcrt.stricmp"
yield "strncasecmp", "msvcrt.strnicmp"
end
end
class Exports::Cygwin < Exports
def self.nm
@@nm ||= RbConfig::CONFIG["NM"]
end
def exports(*)
super()
end
def each_line(objs, &block)
IO.foreach("|#{self.class.nm} --extern-only --defined-only #{objs.join(' ')}", &block)
end
def each_export(objs)
symprefix = RbConfig::CONFIG["SYMBOL_PREFIX"]
symprefix.strip! if symprefix
re = /\s(?:(T)|[[:upper:]])\s#{symprefix}((?!#{PrivateNames}).*)$/
objdump(objs) do |l|
next if /@.*@/ =~ l
yield $2, !$1 if re =~ l
end
end
end
class Exports::Msys < Exports::Cygwin
end
class Exports::Mingw < Exports::Cygwin
def each_export(objs)
super
yield "strcasecmp", "_stricmp"
yield "strncasecmp", "_strnicmp"
end
end
END {
exports = Exports.extract(ARGV)
Exports.output {|f| f.puts(*exports)}
}