dll_windows.go (10522B)
1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package windows 6 7 import ( 8 "sync" 9 "sync/atomic" 10 "syscall" 11 "unsafe" 12 ) 13 14 // We need to use LoadLibrary and GetProcAddress from the Go runtime, because 15 // the these symbols are loaded by the system linker and are required to 16 // dynamically load additional symbols. Note that in the Go runtime, these 17 // return syscall.Handle and syscall.Errno, but these are the same, in fact, 18 // as windows.Handle and windows.Errno, and we intend to keep these the same. 19 20 //go:linkname syscall_loadlibrary syscall.loadlibrary 21 func syscall_loadlibrary(filename *uint16) (handle Handle, err Errno) 22 23 //go:linkname syscall_getprocaddress syscall.getprocaddress 24 func syscall_getprocaddress(handle Handle, procname *uint8) (proc uintptr, err Errno) 25 26 // DLLError describes reasons for DLL load failures. 27 type DLLError struct { 28 Err error 29 ObjName string 30 Msg string 31 } 32 33 func (e *DLLError) Error() string { return e.Msg } 34 35 func (e *DLLError) Unwrap() error { return e.Err } 36 37 // A DLL implements access to a single DLL. 38 type DLL struct { 39 Name string 40 Handle Handle 41 } 42 43 // LoadDLL loads DLL file into memory. 44 // 45 // Warning: using LoadDLL without an absolute path name is subject to 46 // DLL preloading attacks. To safely load a system DLL, use [NewLazySystemDLL], 47 // or use [LoadLibraryEx] directly. 48 func LoadDLL(name string) (dll *DLL, err error) { 49 namep, err := UTF16PtrFromString(name) 50 if err != nil { 51 return nil, err 52 } 53 h, e := syscall_loadlibrary(namep) 54 if e != 0 { 55 return nil, &DLLError{ 56 Err: e, 57 ObjName: name, 58 Msg: "Failed to load " + name + ": " + e.Error(), 59 } 60 } 61 d := &DLL{ 62 Name: name, 63 Handle: h, 64 } 65 return d, nil 66 } 67 68 // MustLoadDLL is like LoadDLL but panics if load operation fails. 69 func MustLoadDLL(name string) *DLL { 70 d, e := LoadDLL(name) 71 if e != nil { 72 panic(e) 73 } 74 return d 75 } 76 77 // FindProc searches DLL d for procedure named name and returns *Proc 78 // if found. It returns an error if search fails. 79 func (d *DLL) FindProc(name string) (proc *Proc, err error) { 80 namep, err := BytePtrFromString(name) 81 if err != nil { 82 return nil, err 83 } 84 a, e := syscall_getprocaddress(d.Handle, namep) 85 if e != 0 { 86 return nil, &DLLError{ 87 Err: e, 88 ObjName: name, 89 Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(), 90 } 91 } 92 p := &Proc{ 93 Dll: d, 94 Name: name, 95 addr: a, 96 } 97 return p, nil 98 } 99 100 // MustFindProc is like FindProc but panics if search fails. 101 func (d *DLL) MustFindProc(name string) *Proc { 102 p, e := d.FindProc(name) 103 if e != nil { 104 panic(e) 105 } 106 return p 107 } 108 109 // FindProcByOrdinal searches DLL d for procedure by ordinal and returns *Proc 110 // if found. It returns an error if search fails. 111 func (d *DLL) FindProcByOrdinal(ordinal uintptr) (proc *Proc, err error) { 112 a, e := GetProcAddressByOrdinal(d.Handle, ordinal) 113 name := "#" + itoa(int(ordinal)) 114 if e != nil { 115 return nil, &DLLError{ 116 Err: e, 117 ObjName: name, 118 Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(), 119 } 120 } 121 p := &Proc{ 122 Dll: d, 123 Name: name, 124 addr: a, 125 } 126 return p, nil 127 } 128 129 // MustFindProcByOrdinal is like FindProcByOrdinal but panics if search fails. 130 func (d *DLL) MustFindProcByOrdinal(ordinal uintptr) *Proc { 131 p, e := d.FindProcByOrdinal(ordinal) 132 if e != nil { 133 panic(e) 134 } 135 return p 136 } 137 138 // Release unloads DLL d from memory. 139 func (d *DLL) Release() (err error) { 140 return FreeLibrary(d.Handle) 141 } 142 143 // A Proc implements access to a procedure inside a DLL. 144 type Proc struct { 145 Dll *DLL 146 Name string 147 addr uintptr 148 } 149 150 // Addr returns the address of the procedure represented by p. 151 // The return value can be passed to Syscall to run the procedure. 152 func (p *Proc) Addr() uintptr { 153 return p.addr 154 } 155 156 //go:uintptrescapes 157 158 // Call executes procedure p with arguments a. It will panic, if more than 15 arguments 159 // are supplied. 160 // 161 // The returned error is always non-nil, constructed from the result of GetLastError. 162 // Callers must inspect the primary return value to decide whether an error occurred 163 // (according to the semantics of the specific function being called) before consulting 164 // the error. The error will be guaranteed to contain windows.Errno. 165 func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { 166 return syscall.SyscallN(p.Addr(), a...) 167 } 168 169 // A LazyDLL implements access to a single DLL. 170 // It will delay the load of the DLL until the first 171 // call to its Handle method or to one of its 172 // LazyProc's Addr method. 173 type LazyDLL struct { 174 Name string 175 176 // System determines whether the DLL must be loaded from the 177 // Windows System directory, bypassing the normal DLL search 178 // path. 179 System bool 180 181 mu sync.Mutex 182 dll *DLL // non nil once DLL is loaded 183 } 184 185 // Load loads DLL file d.Name into memory. It returns an error if fails. 186 // Load will not try to load DLL, if it is already loaded into memory. 187 func (d *LazyDLL) Load() error { 188 // Non-racy version of: 189 // if d.dll != nil { 190 if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll))) != nil { 191 return nil 192 } 193 d.mu.Lock() 194 defer d.mu.Unlock() 195 if d.dll != nil { 196 return nil 197 } 198 199 // kernel32.dll is special, since it's where LoadLibraryEx comes from. 200 // The kernel already special-cases its name, so it's always 201 // loaded from system32. 202 var dll *DLL 203 var err error 204 if d.Name == "kernel32.dll" { 205 dll, err = LoadDLL(d.Name) 206 } else { 207 dll, err = loadLibraryEx(d.Name, d.System) 208 } 209 if err != nil { 210 return err 211 } 212 213 // Non-racy version of: 214 // d.dll = dll 215 atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll)), unsafe.Pointer(dll)) 216 return nil 217 } 218 219 // mustLoad is like Load but panics if search fails. 220 func (d *LazyDLL) mustLoad() { 221 e := d.Load() 222 if e != nil { 223 panic(e) 224 } 225 } 226 227 // Handle returns d's module handle. 228 func (d *LazyDLL) Handle() uintptr { 229 d.mustLoad() 230 return uintptr(d.dll.Handle) 231 } 232 233 // NewProc returns a LazyProc for accessing the named procedure in the DLL d. 234 func (d *LazyDLL) NewProc(name string) *LazyProc { 235 return &LazyProc{l: d, Name: name} 236 } 237 238 // NewLazyDLL creates new LazyDLL associated with DLL file. 239 // 240 // Warning: using NewLazyDLL without an absolute path name is subject to 241 // DLL preloading attacks. To safely load a system DLL, use [NewLazySystemDLL]. 242 func NewLazyDLL(name string) *LazyDLL { 243 return &LazyDLL{Name: name} 244 } 245 246 // NewLazySystemDLL is like NewLazyDLL, but will only 247 // search Windows System directory for the DLL if name is 248 // a base name (like "advapi32.dll"). 249 func NewLazySystemDLL(name string) *LazyDLL { 250 return &LazyDLL{Name: name, System: true} 251 } 252 253 // A LazyProc implements access to a procedure inside a LazyDLL. 254 // It delays the lookup until the Addr method is called. 255 type LazyProc struct { 256 Name string 257 258 mu sync.Mutex 259 l *LazyDLL 260 proc *Proc 261 } 262 263 // Find searches DLL for procedure named p.Name. It returns 264 // an error if search fails. Find will not search procedure, 265 // if it is already found and loaded into memory. 266 func (p *LazyProc) Find() error { 267 // Non-racy version of: 268 // if p.proc == nil { 269 if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc))) == nil { 270 p.mu.Lock() 271 defer p.mu.Unlock() 272 if p.proc == nil { 273 e := p.l.Load() 274 if e != nil { 275 return e 276 } 277 proc, e := p.l.dll.FindProc(p.Name) 278 if e != nil { 279 return e 280 } 281 // Non-racy version of: 282 // p.proc = proc 283 atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc)), unsafe.Pointer(proc)) 284 } 285 } 286 return nil 287 } 288 289 // mustFind is like Find but panics if search fails. 290 func (p *LazyProc) mustFind() { 291 e := p.Find() 292 if e != nil { 293 panic(e) 294 } 295 } 296 297 // Addr returns the address of the procedure represented by p. 298 // The return value can be passed to Syscall to run the procedure. 299 // It will panic if the procedure cannot be found. 300 func (p *LazyProc) Addr() uintptr { 301 p.mustFind() 302 return p.proc.Addr() 303 } 304 305 //go:uintptrescapes 306 307 // Call executes procedure p with arguments a. It will panic, if more than 15 arguments 308 // are supplied. It will also panic if the procedure cannot be found. 309 // 310 // The returned error is always non-nil, constructed from the result of GetLastError. 311 // Callers must inspect the primary return value to decide whether an error occurred 312 // (according to the semantics of the specific function being called) before consulting 313 // the error. The error will be guaranteed to contain windows.Errno. 314 func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { 315 p.mustFind() 316 return p.proc.Call(a...) 317 } 318 319 var canDoSearchSystem32Once struct { 320 sync.Once 321 v bool 322 } 323 324 func initCanDoSearchSystem32() { 325 // https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says: 326 // "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows 327 // Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on 328 // systems that have KB2533623 installed. To determine whether the 329 // flags are available, use GetProcAddress to get the address of the 330 // AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories 331 // function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_* 332 // flags can be used with LoadLibraryEx." 333 canDoSearchSystem32Once.v = (modkernel32.NewProc("AddDllDirectory").Find() == nil) 334 } 335 336 func canDoSearchSystem32() bool { 337 canDoSearchSystem32Once.Do(initCanDoSearchSystem32) 338 return canDoSearchSystem32Once.v 339 } 340 341 func isBaseName(name string) bool { 342 for _, c := range name { 343 if c == ':' || c == '/' || c == '\\' { 344 return false 345 } 346 } 347 return true 348 } 349 350 // loadLibraryEx wraps the Windows LoadLibraryEx function. 351 // 352 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx 353 // 354 // If name is not an absolute path, LoadLibraryEx searches for the DLL 355 // in a variety of automatic locations unless constrained by flags. 356 // See: https://msdn.microsoft.com/en-us/library/ff919712%28VS.85%29.aspx 357 func loadLibraryEx(name string, system bool) (*DLL, error) { 358 loadDLL := name 359 var flags uintptr 360 if system { 361 if canDoSearchSystem32() { 362 flags = LOAD_LIBRARY_SEARCH_SYSTEM32 363 } else if isBaseName(name) { 364 // WindowsXP or unpatched Windows machine 365 // trying to load "foo.dll" out of the system 366 // folder, but LoadLibraryEx doesn't support 367 // that yet on their system, so emulate it. 368 systemdir, err := GetSystemDirectory() 369 if err != nil { 370 return nil, err 371 } 372 loadDLL = systemdir + "\\" + name 373 } 374 } 375 h, err := LoadLibraryEx(loadDLL, 0, flags) 376 if err != nil { 377 return nil, err 378 } 379 return &DLL{Name: name, Handle: h}, nil 380 }