I want to use GetProcAddress to get some functions addresses inside a loaded DLL. I run some attempts on PSAPI library and I can see the the expected proc name is not identical to the symbol names I find with WinDbg x statements. For example, I look for "EnumProcessModules"(which is the name expected by GetProcAddress method) with x psapi!*EnumProcessModules* and I find
00007ff9`fe112210 PSAPI!_imp_K32EnumProcessModulesEx = <no type information>
00007ff9`fe111360 PSAPI!EnumProcessModulesExStub (<no parameter info>)
00007ff9`fe111030 PSAPI!EnumProcessModulesStub (<no parameter info>)
00007ff9`fe1121a8 PSAPI!_imp_K32EnumProcessModules = <no type information>
When I provide any of the found symbols above (with or without "PSAPI!" as a prefix) to the GetProcAddress method as the second parameter (procName) - it returns NULL, but when I use the method name "EnumProcessModules" - it returns 0xfe111030, which is the address of "PSAPI!EnumProcessModulesStub".
How could I know the expected procName in advance? What if I have 2 different classes or namespaces with the same method name in one DLL? How can I distinguish between the two method names when I call GetProcAddress?
PSAPI! is just a prefix, it's the DLL name printed by WinDbg. That's used to disambiguate names. A clear example why this is useful: you will have many DllMain's in your process.
The expected name for GetProcAddress is the documented name of the function, as stated on MSDN. Keep in mind that you will need to add either the A or W suffix when MSDN states both versions are available. E.g. you can't call GetProcAddress with "GetDeviceDriverFileName", you need either "GetDeviceDriverFileNameA" or L"GetDeviceDriverFileNameW".
For non-system DLL's, you need the function name from the Export Address Table.
Background: What you see in WinDbg is the name from the .PDB, which as you have discovered can differ from the exported name. There is nothing which enforces a relation between the two. For instance, it's technically possible to have PDB names Foo and Bar, and have them swapped in the Export Address Table. More realistically, Microsoft may internally add an _wrapper_EnumProcessModules at any time, but the documented and exported name will stay EnumProcessModules.
The Core Functionality for this API is implemented in kernel32.dll
and Named K32EnumProcessModules
in paspi.h header this API is ifdeffed based on PSAPI_VERSION
#ifndef PSAPI_VERSION
#if (NTDDI_VERSION >= NTDDI_WIN7)
#define PSAPI_VERSION 2
#else
#define PSAPI_VERSION 1
#endif
#endif
#if (PSAPI_VERSION > 1)
#define EnumProcessModules K32EnumProcessModules
if you GetProcAddress in psapi.dll you get a stub which gets transferred to kernel32.dll
0:001> u PSAPI!EnumProcessModulesStub l 5
PSAPI!EnumProcessModulesStub:
762d1408 8bff mov edi,edi
762d140a 55 push ebp
762d140b 8bec mov ebp,esp
762d140d 5d pop ebp
762d140e eb05 jmp PSAPI!K32EnumProcessModules (762d1415)
0:001> u 762d1415 l1
PSAPI!K32EnumProcessModules:
762d1415 ff2504102d76 jmp dword ptr [PSAPI!_imp__K32EnumProcessModules (762d1004)]
0:001> u poi(762d1004) l1
kernel32!K32EnumProcessModules:
7668cc52 8bff mov edi,edi
you can use dumpbin /exports on the dll to see if there is any correlation
or if there is a name change (see the Stub#16 )
:\>dumpbin /exports c:\windows\System32\psapi.dll | grep -w EnumProcessModules
5 4 00001408 EnumProcessModules = _EnumProcessModulesStub#16
you could also find the same info from the export table of psapi.dll using some thing like below
0:001> .shell -ci "!dh psapi" grep Export
1088 [ 359] address [size] of Export Directory
.shell: Process exited
0:001> dt ole32!_IMAGE_EXPORT_DIRECTORY (psapi + 1088)
+0x000 Characteristics : 0
+0x004 TimeDateStamp : 0x4a5bc026
+0x008 MajorVersion : 0
+0x00a MinorVersion : 0
+0x00c Name : 0x11be
+0x010 Base : 1
+0x014 NumberOfFunctions : 0x1b
+0x018 NumberOfNames : 0x1b
+0x01c AddressOfFunctions : 0x10b0
+0x020 AddressOfNames : 0x111c
+0x024 AddressOfNameOrdinals : 0x1188
0:001> r? $t0 = (int *) ##(psapi + 10b0)
0:001> r? $t1 = (int *) ##(psapi + 111c)
0:001> r? $t2 = (short *) ##(psapi + 1188)
0:001> .printf "%x %ma %y\n" , ##(#$t2[4]) , (##(#$t1[4]) + psapi) , (##(#$t0[4]) + psapi)
4 EnumProcessModules PSAPI!EnumProcessModulesStub (762d1408)
Related
The tree is as follows
module M
type A
real , allocatable :: dd (:)
end type A
type B
type (A) , pointer :: aa
end type B
type C
type(B) , pointer :: bb
end type C
type(C) , allocatable :: cc(:)
end module M
In gdb:
print M::cc(10) % bb % aa % dd(100)
$1 = 0
However,
info address M::cc(10) % bb % aa % dd (100)
generates the error: No symbol "cc(10) % bb % aa% dd (100)" in current context.
whatis address M::cc(10) % bb % aa% dd(100)
generates the error: Cannot access memory at address 0xa0
I see that the compiler flag "-g" has to be specified to access global variables but that didn't help.
The reason I need the address is to watch M::cc(10) %bb % aa % dd (100) with a software watchpoint so that I don't slow gdb drastically. I'm hoping that this would give me insight into the lines of code which change the value of dd (100).
One option that I've already explored is to just go ahead and set the watch point with:
watch cc(10) % bb % aa % dd ( 100 )
But this results in several hundred watchpoints with Numbers 1.1 , 1.2, 1.3 .... 1.500! This is really slowing down the execution in gdb.
So how could I access the address? If that is not possible, what is the best alternative?
I was trying to learn how to set conditional break point inside windbg, I've program named ConsoleApplication7 like below:
int main()
{
int r1 = 0;
r1 += 1;
r1 = 3;
return 0;
}
I compile this program with VC. Then in windbg, I setup the "Symbol path" and "Source path", open ConsoleApplication7.exe inside windbg, windbg runs and opens the .cpp file.
Inside windbg I set a conditional break point, I wish the program should when in line "r1=3".
bp consoleapplication7!main "j (poi(r1)>2) ''; 'gc'"
But when I press "g", windbg doesn't say it "Hit breakpoint", but just print out registers information. After running "g" for 3 times, the program terminate, like below:
0:000> bp consoleapplication7!main "j (poi(r1)>2) ''; 'gc'"
*** WARNING: Unable to verify checksum for ConsoleApplication7.exe
0:000> bl
0 e 01251380 0001 (0001) 0:**** ConsoleApplication7!main "j (poi(r1)>2) ''; 'gc'"
0:000> g
eax=00718918 ebx=7efde000 ecx=0071b880 edx=00000001 esi=00000000 edi=00000000
eip=01251380 esp=002afeb4 ebp=002aff00 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
ConsoleApplication7!main:
01251380 55 push ebp
0:000> g
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=77c02100 edi=77c020c0
eip=77b1fcd2 esp=002afe18 ebp=002afe34 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!NtTerminateProcess+0x12:
77b1fcd2 83c404 add esp,4
0:000> g
^ No runnable debuggees error in 'g'
Other commands like ba, bp works well in the program, just conditional break point doesn't.
Is my conditional statement correct? Why it doesn't work?
I tried the suggestion from Dono's answer, but it shows another problem
0:000> bp ConsoleApplication7!main+0x3e "j (poi(r1) > 2) ''; 'gc'"
0:000> bl
0 e 008e143e 0001 (0001) 0:**** ConsoleApplication7!main+0x3e "j (poi(r1) > 2) ''; 'gc'"
0:000> g
(4458.4840): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=7efde000 ecx=d3392f75 edx=00000001 esi=00000000 edi=00000000
eip=cccccccc esp=0046f82c ebp=cccccccc iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
cccccccc ?? ???
Seems this conditional break point leads to an exception?
Thanks!
I also tried Blabb's suggestion, unluckily, it didn't work:
0:000> t "$$>a< c:\\autostep.txt"
Couldn't resolve error at 'r1) == 3 ) { dv } .else { t "$$>a< c:\\autostep.txt" } '
eax=00000001 ebx=7efde000 ecx=00000000 edx=00000001 esi=00000000 edi=001dfcb0
eip=013b13c0 esp=001dfbac ebp=001dfcb0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
ConsoleApplication7!f:
013b13c0 55 push ebp
Seems C++ style command didn't work, so I changed it to MASM style
j (poi(r1)==3)''; 't "$$>a< c:\\autostep.txt"'
Save as c:\autostep.txt
Then I restart windbg to reload it.
0:000> t "$$>a< c:\\autostep.txt"
eax=00000000 ebx=00000000 ecx=bc260000 edx=0015dc38 esi=fffffffe edi=00000000
eip=77ba0e15 esp=0042f804 ebp=0042f830 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000244
ntdll!LdrpDoDebuggerBreak+0x2d:
77ba0e15 8975fc mov dword ptr [ebp-4],esi ss:002b:0042f82c=00000000
0:000> g
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=77c02100 edi=77c020c0
eip=77b1fcd2 esp=0042fb94 ebp=0042fbb0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!NtTerminateProcess+0x12:
77b1fcd2 83c404 add esp,4
This time, it has no syntax issue, when it doesn't break/stop as I expected. Did I miss anything?
Thanks.
First of all you should be aware that your program will be optimized away to just xor eax,eax ; ret
Unless you compile in debug mode and explicitly disable optimizations with /Od switch
( I assume you are using visual monster project in debug mode which takes care of this )
Second you should be aware is that debugger does not know your r1 r2 or whatever unless symbol information is present and event then you should be aware that there are two expression evaluator MASM and C++ and by default windbg uses MASM
? poi(r1) == MASM evaluation
?? r1 == c++ evaluation
other wise debugger understands only the variables that cpu understands like BYTE , WORD , DWORD , QWORD .... etc and addresses
when you set conditional breakpoint like bp xxxxxmodule!yyyyysymbol "condition" conditions are evaluated only on the address specified not further below
so your condition was evaluated only on main
you should step further down and evaluate your condition on each step until you encounter a match
your code compiled and main() disassembled will look like this
0:000> .dml_flow steptest!main .
<No previous node>
STEPTEST!main (013e6aa0):
e:\test\steptest\steptest.cpp
2 013e6aa0 push ebp
2 013e6aa1 mov ebp,esp
2 013e6aa3 push ecx
3 013e6aa4 mov dword ptr [ebp-4],0
4 013e6aab mov eax,dword ptr [ebp-4]
4 013e6aae add eax,1
4 013e6ab1 mov dword ptr [ebp-4],eax
5 013e6ab4 mov dword ptr [ebp-4],3
6 013e6abb xor eax,eax
7 013e6abd mov esp,ebp
7 013e6abf pop ebp
7 013e6ac0 ret
<No next node>
0:000> lsa .
1: int main()
2: {
3: int r1 = 0;
> 4: r1 += 1;
5: r1 = 3;
6: return 0;
7: }
so your condition can be true only on line 6 or in assembly at address 013e6abb xor eax, eax
you set a conditional bp on 013e6aa0 and the condition was evaluated at that address only
you need to find a way to step till 013e6abb
that is you should
step <evaluate> if match stop else repeat step and evaluate until match occurs
to do that put the following line in a file named autostep.txt and save it to c:\
explanation of the script
if the c++ expression r1 equals 3 show the locals else step and reevaluate by running this script again ( so this script will be executed on each instruction sequence until a match is found )
.if ( ##c++(r1) == 3 ) { dv } .else { t "$$>a< c:\\autostep.txt" }
now open the exe in windbg and do
bp xxxxmod!main
g
t "$$>a< c:\\autostep.txt"
windbg will break when r1 is 3
E:\test\STEPTEST>cdb -c "g steptest!main" STEPTEST.exe
0:000> cdb: Reading initial command 'g steptest!main'
STEPTEST!main:
013e6aa0 55 push ebp
0:000> t "$$>a< c:\\autostep.txt"
r1 = 0n3
013e6abb 33c0 xor eax,eax
0:000> ?? r1
int 0n3
0:000> ? poi(r1)
Evaluate expression: 3 = 00000003
0:000> ? &main ; ? &#eip
Evaluate expression: 20867744 = 013e6aa0
Evaluate expression: 20867771 = 013e6abb
0:000>
you can invoke the script in cmd.exe
cat c:\autostep.txt
.if ( ##c++(r1) == 3 ) { dv } .else { t "$$>a< c:\\autostep.txt" }
cdb -c "g steptest!main;$$>a< c:\autostep.txt" STEPTEST.exe
0:000> cdb: Reading initial command 'g steptest!main;$$>a< c:\autostep.txt'
r1 = 0n3
eax=00000001 ebx=7ffd6000 ecx=a4894de4 edx=00000001 esi=00a2dd04 edi=00a2dd08
eip=009d6abb esp=0019f8d0 ebp=0019f8d4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
STEPTEST!main+0x1b:
009d6abb 33c0 xor eax,eax
0:000>
Your command means: Unconditionally break at the start of the main function. Then, if r1 > 2, do nothing; otherwise, continue.
Note that bp takes an address and breaks on it. Because you have symbols (PDB), you are able to convert the symbol "consoleapplication7!main" into an address. This points to the start of the function, where r1 has not yet even been initialized, so there is no way for it to be > 2, except for random garbage values.
So first you need to break at a more reasonable spot. There are multiple ways to do this. You can "unscramble" the assembly code uf consoleapplication7!main and determine the offset address to break. Something like bp ConsoleApplication7!main+0x35 "j (poi(r1) > 2) ''; 'gc'". This may be a little difficult. An easier method is to specify the line number such as bp `driver.cpp:6` "j (poi(r1) > 2) ''; 'gc'", assuming your main function is in a file called driver.cpp.
Also, you need to careful whether your application was compiled for debug or release mode. In release mode, most of your main function can be optimized away as the value of r1 can be pre-computed at compile time. This will naturally affect the offsets.
And finally, unless you have a habit of using j to mean "if", I would suggest the more modern .if (condition) { commands } .else { commands } syntax. While they do the same thing, the later is much more readable.
See here for further details.
I just read this excellent article: http://neugierig.org/software/chromium/notes/2011/08/static-initializers.html
and then I tried: https://gcc.gnu.org/onlinedocs/gccint/Initialization.html
What it says about finding initializers does not work for me though. The .ctors section is not available, but I could find .init_array (see also Can't find .dtors and .ctors in binary). But how do I interpret the output? I mean, summing up the size of the pages can also be handled by the size command and its .bss column - or am I missing something?
Furthermore, nm does not report any *_GLOBAL__I_* symbols, only *_GLOBAL__N_* functions, and - more interesting - _GLOBAL__sub_I_somefile.cpp entries. The latter probably indicates files with global initialization. But can I somehow get a list of constructors that are being run? Ideally, a tool would give me a list of
Foo::Foo in file1.cpp:12
Bar::Bar in file2.cpp:45
...
(assuming I have debug symbols available). Is there such a tool? If not, how could one write it? Does the .init_array section contain pointers to code which could be translated via some DWARF magic to the above?
As you already observed, the implementation details of contructors/initialization functions are highly compiler (version) dependent. While I am not aware of a tool for this, what current GCC/clang versions do is simple enough to let a small script do the job: .init_array is just a list of entry points. objdump -s can be used to load the list, and nm to lookup the symbol names. Here's a Python script that does that. It should work for any binary that was generated by the said compilers:
#!/usr/bin/env python
import os
import sys
# Load .init_array section
objdump_output = os.popen("objdump -s '%s' -j .init_array" % (sys.argv[1].replace("'", r"\'"),)).read()
is_64bit = "x86-64" in objdump_output
init_array = objdump_output[objdump_output.find("Contents of section .init_array:") + 33:]
initializers = []
for line in init_array.split("\n"):
parts = line.split()
if not parts:
continue
parts.pop(0) # Remove offset
parts.pop(-1) # Remove ascii representation
if is_64bit:
# 64bit pointers are 8 bytes long
parts = [ "".join(parts[i:i+2]) for i in range(0, len(parts), 2) ]
# Fix endianess
parts = [ "".join(reversed([ x[i:i+2] for i in range(0, len(x), 2) ])) for x in parts ]
initializers += parts
# Load disassembly for c++ constructors
dis_output = os.popen("objdump -d '%s' | c++filt" % (sys.argv[1].replace("'", r"\'"), )).read()
def find_associated_constructor(disassembly, symbol):
# Find associated __static_initialization function
loc = disassembly.find("<%s>" % symbol)
if loc < 0:
return False
loc = disassembly.find(" <", loc)
if loc < 0:
return False
symbol = disassembly[loc+2:disassembly.find("\n", loc)][:-1]
if symbol[:23] != "__static_initialization":
return False
address = disassembly[disassembly.rfind(" ", 0, loc)+1:loc]
loc = disassembly.find("%s <%s>" % (address, symbol))
if loc < 0:
return False
# Find all callq's in that function
end_of_function = disassembly.find("\n\n", loc)
symbols = []
while loc < end_of_function:
loc = disassembly.find("callq", loc)
if loc < 0 or loc > end_of_function:
break
loc = disassembly.find("<", loc)
symbols.append(disassembly[loc+1:disassembly.find("\n", loc)][:-1])
return symbols
# Load symbol names, if available
nm_output = os.popen("nm '%s'" % (sys.argv[1].replace("'", r"\'"), )).read()
nm_symbols = {}
for line in nm_output.split("\n"):
parts = line.split()
if not parts:
continue
nm_symbols[parts[0]] = parts[-1]
# Output a list of initializers
print("Initializers:")
for initializer in initializers:
symbol = nm_symbols[initializer] if initializer in nm_symbols else "???"
constructor = find_associated_constructor(dis_output, symbol)
if constructor:
for function in constructor:
print("%s %s -> %s" % (initializer, symbol, function))
else:
print("%s %s" % (initializer, symbol))
C++ static initializers are not called directly, but through two generated functions, _GLOBAL__sub_I_.. and __static_initialization... The script uses the disassembly of those functions to get the name of the actual constructor. You'll need the c++filt tool to unmangle the names, or remove the call from the script to see the raw symbol name.
Shared libraries can have their own initializer lists, which would not be displayed by this script. The situation is slightly more complicated there: For non-static initializers, the .init_array gets an all-zero entry that is overwritten with the final address of the initializer when loading the library. So this script would output an address with all zeros.
There are multiple things executed when loading an ELF object, not just .init_array. To get an overview, I suggest looking at the sources of libc's loader, especially _dl_init() and call_init().
I have been developing a Bootloader and have run into a problem when linking c++ code to my assembly stage2 code before I linked the files the second stage would jump to protected mode then to long mode without any problems but now after I have linked it there seems to be a problem when jumping to protected mode Here is the code I use to jump to protected mode:
main:
;first stage of bootloader is loaded at the address 0x07c0:0
;second stage of bootloader is loaded at address 0x200:0x0
cli
xor ax, ax ; All segments set to 0, flat memory model
mov ds, ax
mov es, ax
mov gs, ax
mov fs, ax
mov ss, ax
;
; Set stack top SS:0xffff
;
mov sp, 0x0FFFF
;
mov [CDDriveNumber], dl
SwitchToProtectedMode:
lgdt [GDT_32];load the gdt
call EnableA20
mov eax, cr0
or eax, 1
mov cr0, eax
; Flush CS and set code selector
;
jmp 0x8:Protected_Mode
[BITS 32];Declare 32 bits
Protected_Mode:
Here is the GDT:
GDT_START:
;null descriptor
dd 0
dd 0
;data descriptor
dw 0xFFFF
dw 0
db 0
db 10011010b
db 11001111b
db 0
;code descriptor
dw 0xFFFF
dw 0
db 0
db 10010010b
db 11001111b
db 0
GDT_END:
align 4
GDT_32:
dw GDT_END - GDT_START - 1
dd GDT_START
Here is the linker script I use to link my c and assembly code
KernAddr = 0x200;
ENTRY(_Start)
SECTIONS
{
. = KernAddr;
.text : AT(ADDR(.text) - KernAddr)
{
_code = .;
*(.text)
*(.rodata*)
. = ALIGN(4096);
}
.data : AT(ADDR(.data) - KernAddr)
{
_data = .;
*(.data)
. = ALIGN(4096);
}
.eh_frame : AT(ADDR(.eh_frame) - KernAddr)
{
_ehframe = .;
*(.eh_frame)
. = ALIGN(4096);
}
.bss : AT(ADDR(.bss) - KernAddr)
{
_bss = .;
*(.bss)
/*
* You usually need to include generated COMMON symbols
* under kernel BSS section or use gcc's -fno-common
*/
*(COMMON)
. = ALIGN(4096);
}
_end = .;
/DISCARD/ :
{
*(.comment)
}
}
Here is the batch program I made to build everything:
nasm Stage1.asm -o Stage1.bin
nasm -f elf64 Stage2.asm -o Stage2.o
x86_64-elf-g++ -ffreestanding -mcmodel=large -mno-red-zone -mno-mmx -mno-sse -mno-sse2 -mno-sse3 -mno-3dnow -c -o kernel.o kernel.cpp
x86_64-elf-ld -T linkerscript.ld -o Anmu.bin Stage2.o kernel.o -nostdlib
copy Stage1.bin Root
copy Anmu.bin Root
mkisofs -b Stage1.bin -no-emul-boot -boot-info-table -o BootLoader.iso ./Root
The rest of the code is available here: https://github.com/AnonymousUser1337/Anmu
You say that Stage2 is loaded at segment 0x0200, which is address 0x2000, but your linker says it starts at offset 0x0200.
Also, despite naming your output "Anmu.bin", your file is actually still an ELF executable, which means all of the headers and whatnot are still present in the file. Instead, you need to use objcopy to strip all of the headers and debugging symbols, giving you a flat binary:
objcopy -S -O binary Anmu.bin Anmu-flat.bin
Now, "Anmu-flat.bin" is nothing but code and data, and the first byte of the file is the start of the first instruction.
Please excuse my newbie question but when I tried exporting a function in the header using
__declspec(dllexport) void testfunction(double i);
and declared the function in the .cpp file like this
void testfunction(double i) {
for (int k = 0; k<10; k++) {
double j = 0.1;
}
}
I only see this for the function after disassembling the .exe file using IDA pro:
.text:00401130 ; void __cdecl testfunction(double)
.text:00401130 public ?testfunction##YAXN#Z
.text:00401130 ?testfunction##YAXN#Z proc near ; DATA XREF: .rdata:off_40BE88o
.text:00401130 jmp dword_40C000
.text:00401130 ?testfunction##YAXN#Z endp
Below I have provided the location of dword_40C000
.data:0040C000 ; Section 3. (virtual address 0000C000)
.data:0040C000 ; Virtual size : 000004A0 ( 1184.)
.data:0040C000 ; Section size in file : 00000200 ( 512.)
.data:0040C000 ; Offset to raw data for section: 0000B200
.data:0040C000 ; Flags C0000040: Data Readable Writable
.data:0040C000 ; Alignment : default
.data:0040C000 ; ===========================================================================
.data:0040C000
.data:0040C000 ; Segment type: Pure data
.data:0040C000 ; Segment permissions: Read/Write
.data:0040C000 _data segment para public 'DATA' use32
.data:0040C000 assume cs:_data
.data:0040C000 ;org 40C000h
.data:0040C000 dword_40C000 dd 6000001h ; DATA XREF: testfunction(double)r
.data:0040C004 align 10h
.data:0040C010 db 2
.data:0040C011 db 0
.data:0040C012 db 0
.data:0040C013 db 0
.data:0040C014 db 2
.data:0040C015 db 0
.data:0040C016 db 0
.data:0040C017 db 0
.data:0040C018 dword_40C018 dd 6000003h ; DATA XREF: .text:004011A0r
.data:0040C01C dword_40C01C dd 6000004h ; DATA XREF: .text:004011D0r
.data:0040C020 dword_40C020 dd 6000005h ; DATA XREF: .text:00401200r
...
Shouldn't there be more code since I have a for loop and some other stuff? Where is the actual code?
It seems you're using C++/CLI. I think the 6000001 is the managed method's token, it will be replaced by the address of the JITted code at runtime.
Compile your binary for native x86 to see machine code.