Skip to content

Instantly share code, notes, and snippets.

@jephthai
Created June 9, 2019 22:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jephthai/070873cf8542d13186ed673cd325db19 to your computer and use it in GitHub Desktop.
Save jephthai/070873cf8542d13186ed673cd325db19 to your computer and use it in GitHub Desktop.
wincon - win32 constant resolver
#!/usr/bin/env ruby
#
# Often, when writing code that interfaces with the Win32 API, I need
# to know the numerical value of constants defined in various headers.
# Microsoft documents some, but others are conspicuously absent. It's
# no fun to grep through *.h files, trying to find it. And keeping
# Visual Studio around and open solely to hover over constant names
# to see their value is a pain in a Linux dev environment.
#
# So this program uses the MinGW cross compiler to look up constant
# values for you. In some cases, they may be numeric #defines,
# whereas other times they may be an index in an enum or an expression
# that resolves to a constant at compile time.
#
# This program shows you the expression that gets macro-expanded, but
# also tries to compile a program, storing the constant in a uint64_t.
# In most cases, from these two sources you can find the actual value
# for the constant.
#
# E.g., if you want to use the "PROCESS_ALL_ACCESS" constant when you
# call OpenProcess() (it's not shown in the MSDN docs!), you can do
# this:
#
# $ wincon PROCESS_ALL_ACCESS
#
# Consulting the MinGW oracle...
#
# Constant: PROCESS_ALL_ACCESS
# Lexical value: ((0x000F0000l) | (0x00100000l) | 0xfff)
# uint64_t value: dec: 2035711 hex: 0x1f0fff bin: 0b111110000111111111111
#
# Forth def: $1f0fff value PROCESS_ALL_ACCESS
#
# Add other include files if you need more than just "windows.h".
# Here is one I ran into once, from iphlpapi.dll:
#
# $ wincon iphlpapi.h TCP_TABLE_BASIC_CONNECTIONS
#
# Consulting the MinGW oracle...
#
# Constant: TCP_TABLE_BASIC_CONNECTIONS
# Lexical value: TCP_TABLE_BASIC_CONNECTIONS
# uint64_t value: dec: 1 hex: 0x1 bin: 0b1
#
# Forth def: $1 value TCP_TABLE_BASIC_CONNECTIONS#
#
#
require 'tempfile'
$compiler = "x86_64-w64-mingw32-gcc"
#
# Use the SHIFT-IN and SHIFT-OUT characters to add some color for
# fields when printing data out to stdout.
#
def pputs(msg = "", color=32)
msg.gsub!("", "\x1b[#{color};1m")
msg.gsub!("", "\x1b[0m")
puts(msg)
end
#
# We need at least the constant to be evaluated, but we can also
# take any number of additional #include<...> files before that
# name too.
#
if ARGV.length == 0
pputs
pputs " usage: wincon [ <include> ] <constant>"
pputs
pputs " Print value of <constant> after including the indicated"
pputs " header files. Uses the MinGW cross compiler, and assumes"
pputs " 64-bit compilation."
pputs
pputs " Note: windows.h will be included by default, so adding"
pputs " on the command line would be silly."
pputs
pputs " Example:"
pputs
pputs " $ wincon SC_MANAGER_ALL_ACCESS"
pputs
exit 1
end
#
# Make sure the MinGW cross compiler is in the path. If not, we
# can't very well decode Win32 header constants.
#
`which #{$compiler} 2>&1 >/dev/null`
unless $?.success?
pputs
pputs(" ERROR: need to have #{$compiler} in $PATH")
pputs
exit 2
end
#
# Take a list of include files and make them valid preprocessor
# macros for the compiler to consume.
#
def include_syntax(includes)
code = ""
includes.each { |include| code += "#include <#{include}>\n" }
return code
end
#
# Compile a program with MinGW. 'mode' decides whether we just
# send it through the preprocessor, or continue on to assembly
# code generation.
#
def program(code, mode = :preprocess)
cflags = { :preprocess => "-E", :compile => "-S" }
source = Tempfile.new("source")
begin
source.write(code)
source.close
result = `#{$compiler} -O0 #{cflags[mode]} -o - -xc #{source.path}`.chomp
return result
ensure
source.unlink
end
end
#
# Construct a program whose last line will be the macro-expanded
# value of our target constant.
#
def lexical_program(includes, value)
begin
code = include_syntax(includes)
code += "#{value}\n"
output = program(code, :preprocess)
return output.split(/\n/)[-1]
rescue
return "Unknown"
end
end
#
# Construct a program that stores the value of our constant in an
# integer field so we can extract it.
#
def int_value_program(includes, value)
begin
code = include_syntax(includes)
code += "static uint64_t value = (uint64_t)(#{value});\n"
code += "int main() { return (int)value; }\n"
output = program(code, :compile).split(/\n/)
lineno = output.find_index { |i| i =~ /^value:/ } + 1
return output[lineno].split()[1].to_i
rescue Exception => e
return "Unknown #{e.message}"
end
end
#
# Program logic begins here
#
includes = ["windows.h", "stdint.h", *ARGV[0...-1]]
constant = ARGV[-1]
pputs
pputs(" Consulting the MinGW oracle...")
pputs
lexical = lexical_program(includes, constant)
intval = int_value_program(includes, constant)
if intval.is_a?(Fixnum)
bases = sprintf("dec: %d hex: 0x%x bin: 0b%b", intval, intval, intval)
forth = sprintf("$%x value %s", intval, constant)
color = 32
else
bases = "ERROR"
forth = "ERROR"
color = 31
end
pputs(" Constant: #{constant}", 34)
pputs(" Lexical value: #{lexical}", 34)
pputs(" uint64_t value: #{bases}", color)
pputs
pputs(" Forth def: #{forth}", color)
pputs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment