“This is the first day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

preface

Objective-c and C languages often use Pointers. The data types in Swift are designed to work seamlessly with the cursor-based C API. But there are big differences in grammar.

Swift is memory safe by default, and Apple does not officially encourage direct manipulation of memory. However, Swift also provides a way to manipulate memory using Pointers. Manipulating memory directly is dangerous and error-prone. Therefore, it is officially called unsafe.

Why are Pointers unsafe?

  • For example, when we create an object, we need to allocate memory space in the heap. When the object to which the pointer is pointed is released or reclaimed, but no changes are made to the pointer so that the pointer still points to the memory address that has been reclaimedWild pointer;
  • For example, if we create an array of size 20, we can access index = 25 through a pointerCrossing the line, accesses an unknown memory space;
  • Pointer type and memory valuetypeInconsistent, also unsafe;

Before operating Pointers, understand the size, stride, and alignment attributes of MemoryLayout.

The introduction of MemoryLayout

Gets the size, in bytes, of memory occupied by a particular type.

// The space occupied in memory

MemoryLayout<T>.size 
Copy the code

// The actual size of memory allocated for type T (memory alignment is followed for better performance)

MemoryLayout<T>.stride 
Copy the code

// The cardinality of memory alignment (to improve performance, any object is memory-aligned before it is used)

MemoryLayout<T>.alignment  
Copy the code

For example, here is the corresponding size of the three:

struct Point {
  let x: Double
  let y: Double
  let isFilled: Bool
}

print(MemoryLayout<Point>.size)//17
print(MemoryLayout<Point>.stride)//24
print(MemoryLayout<Point>.alignment)//8
Copy the code

X and y are types Double and fill 8 bytes. IsFilled is a Bool and fill 1 byte

The stride size is 24 because it follows the 8-byte memory alignment principle

The alignment size is 8 because the memory size of the Double data that occupies the most space in this structure is 8

Therefore, in the process of pointer movement, one stride(step size) is moved at a time, and the stride size is an integer multiple of the alignment.

Pointer explanation and use

A pointer encapsulates a memory address. Since the Swift language considers memory unsafe to operate on, Pointers are prefixed with the unsafe prefix UnsafePointer.

Swift uses a very clear name for pointer types. Through searching, we found a total of 8 types of Pointers, including the following:

  • Pointer Name: Immutable/mutable, raw/typed, whether it is a buffer type
  • Unsafe, isn’t it
  • Strideable: Pointers can be moved using the advanced function
  • Write Access: Write is available
  • Collection: Acts like a container for adding data
  • Typed: Whether the type needs to be specified (generic)

The use of UnsafePointer

For example: create a memory space to store four ints.

UnsafePointer cannot modify what the pointer points to, so use UnsafeMutableRawPointer

// Get step size is small
let stride = MemoryLayout<Int>.stride

// Open up 32 bytes of space, 8 bytes of memory alignment
let  ptr = UnsafeMutableRawPointer.allocate(byteCount: 4*stride, alignment: 8)

for i in 1..<5 {
    ptr.advanced(by: i*stride).storeBytes(of: i, as: Int.self)}for i in 1..<5 {
    let value = ptr.load(fromByteOffset: i*stride, as: Int.self)
    print("index\(i)=value:\(value)")}Copy the code

Remember that you can free up the requested memory space in defer. Defer will be called before the function returns, requiring a deallocate call on the surface.

The code used above explains:

  • Allocate: Method to allocate the number of bytes required
  • ByteCount: Indicates the total number of bytes occupied
  • Alignment: Indicates the type of memory alignment
  • Advanced: Moves the pointer address
  • StoreBytes: storage bytes
  • Load: reads bytes
  • FromByteOffset: indicates the address of the offset pointer

Use of generic Pointers

A generic pointer, in contrast to a native pointer, specifies that the current pointer is bound to a specific type. And again, let me just give you an example.

In the case of generic pointer access, we do not use the load and store methods for storage operations. Here we use the variable pointee built into the current generic pointer, which can read and store values in a type-safe manner.

There are two ways to get UnsafePointer

  • Get from an existing variable
var a = 20

// Access the address of the current variable with withUnsafePointer
withUnsafePointer(to: &a) { ptr in
    print(ptr)
}

// If you want to change the current value of a, you can return the current change
a = withUnsafePointer(to: &a) {ptr in
    ptr.pointee + 20
}

withUnsafePointer(to: &a) { ptr in
    print(ptr)
}

var b = 10
// The value can be changed directly with withUnsafeMutablePointer
withUnsafeMutablePointer(to: &b) { ptr in
    ptr.pointee + = 10
}
Copy the code

  • Direct allocation of memory
var a = 10

// Allocate a block of memory of type Int
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)

// Initialize the allocated memory space
ptr.initialize(to: a)

// Access the current memory space value
print(ptr.pointee)

// Reclaim memory after use
defer {
    ptr.deinitialize(count: 1)
    ptr.deallocate()
}
Copy the code

Memory binding

Swift provides three different apis to bind/rebind Pointers:

1. assumingMemoryBound(to: )

The assumption memorybound (to:) can be used to tell the compiler what type to expect from the assumption memorybound (to:). (Note: this is just a way for the compiler to bypass type checking, no conversion of the actual type takes place.)

Here’s an example:

The compiler checks that the current pointer stores values of different types and cannot be converted

func testPointer(_ ptr: UnsafePointer<Int>) {
    print(ptr[0])
    print(ptr[1])}// A tuple is a value type. Essentially, this space holds data of type Int
let tuple = (10.20)
withUnsafePointer(to: tuple) { (tuplePtr: UnsafePointer< (Int.Int) >)in
    // Convert to a native pointer, and then tell the compiler that the current memory is bound to Int, which the compiler will no longer check
    testPointer(UnsafeRawPointer(tuplePtr).assumingMemoryBound(to: Int.self))}Copy the code

This is equivalent to bypassing the compiler’s check, left and right will not report errors.

2. bindMemory(to: capacity:)

Used to change the type of memory binding, if the memory does not already have a type binding, will bind to that type for the first time; Otherwise, rebind the type and all the values in memory will change to that type.

func testPointer(_ ptr: UnsafePointer<Int>) {
    print(ptr[0])
    print(ptr[1])}let tuple = (10.20)

withUnsafePointer(to: tuple) { (tuplePtr: UnsafePointer< (Int.Int) >)in
    testPointer(UnsafeRawPointer(tuplePtr).bindMemory(to: Int.self, capacity: 2))}Copy the code

3. withMemoryRebound(to: capacity: body:)

When we pass parameters to external functions, there are inevitably some data type gaps. If we cast, we have to copy data back and forth; We can use withMemoryRebound(to: Capacity: body:) to temporarily change the memory binding type.

func testPointer(_ ptr: UnsafePointer<Int8>){
    print(ptr)
}

let uint8Ptr = UnsafePointer<UInt8>.init(bitPattern: 20)

uint8Ptr?.withMemoryRebound(to: Int8.self, capacity: 1, { (int8Ptr: UnsafePointer<Int8>) in
    testPointer(int8Ptr)
})
Copy the code

The use of UnsafeBufferPointer

UnsafeBufferPointer refers to a sequence of consecutive memories. We can use this pointer as a pointer to a sequence and access elements directly through the pointer.

let pointer = UnsafeMutablePointer<Int>.allocate(capacity: 2)
pointer.pointee = 42
pointer.advanced(by: 1).pointee = 6

let bufferPointer = UnsafeBufferPointer(start: pointer, count: 2)
for (index, value) in bufferPointer.enumerated() {
    print("value \(index): \(value)")}Copy the code

Print result:

value 0: 42

value 1: 6

The use of UnsafeMutableBufferPointer

Mutable series pointer. UnsafeMutableBufferPointer have to modify the ability to sequence.

let usmp = UnsafeMutablePointer<Int>.allocate(capacity: 1)
let usmbp = UnsafeMutableBufferPointer<Int>.init(start: usmp, count: 5// Expand to 5 elements

usmbp[0] = 120
usmbp[1] = 130   // Random values are generated for other unmodified items

usmbp.forEach { (a) in
       print(a)
}
 print(usmbp.count)
Copy the code

If a sequence is initialized and each element is not assigned a value, the values of those elements will be random.

UnsafeRawBufferPointer and UnsafeMutableRawBufferPointer

UnsafeRawBufferPointer and UnsafeMutableRawBufferPointer refer to is a series of memory area is not binding type. We can think of them as arrays that contain each byte before memory binding.

let pointer = UnsafeMutableRawBufferPointer.allocate(byteCount: 3, alignment: MemoryLayout<Int>.alignment)

pointer.copyBytes(from: [1.2.3])
pointer.forEach {
    print($0) / / 1, 2, 3
}
Copy the code

So reading is a kind of from the original buffer from memory type operation, UnsafeMutableRawBufferPointer instance can be written to memory, can’t UnsafeRawBufferPointer instance. To type, memory must be bound to a type.

Conversion between Pointers

The unsafeBitCast function is a cast that ignores data types and does not change the original memory data due to data type changes, similar to reinterpret_cast in C++.

var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
ptr.assumingMemoryBound(to: Int.self).pointee = 20
(ptr + 8).assumingMemoryBound(to: Double.self).pointee = 10.0

print(unsafeBitCast(ptr, to: UnsafePointer<Int>.self).pointee) / / 20
print(unsafeBitCast(ptr + 8, to: UnsafePointer<Double>.self).pointee) / / 10.0

ptr.deallocate()
Copy the code

The above code stores 20 Int and 10.0 Double into the memory of the PTR pointer. At the time of the value, we can use unsafeBitCast to fetch their values separately.

Pointer strengthening exercise

Among the methods and attributes we learned earlier, we found attribute-related information in the Mach-O file using source code + assembly analysis. Next we use Pointers to get information about attributes in the Mach-O file.

Here is the underlying structure of the sorted class:

class Person {
    var age: Int = 18
    var name: String = "Candy"
}

// Description of the class
struct TargetClassDescriptor {
    var flags: UInt32
    var parent: UInt32
    var name: Int32
    var accessFunctionPointer: Int32
    var fieldDescriptor: Int32
    var superClassType: Int32
    var metadataNegativeSizeInWords: UInt32
    var metadataPositiveSizeInWords: UInt32
    var numImmediateMembers: UInt32
    var numFields: UInt32
    var fieldOffsetVectorOffset: UInt32
    var Offset: UInt32
    var size: UInt32
    //V-Table
}

// Attribute information
struct FieldDescriptor {
    var MangledTypeName: UInt32
    var Superclass: UInt32
    var Kind: UInt16
    var FieldRecordSize: UInt16
    var NumFields: UInt32
    var FieldRecords: FieldRecord
}

// Attribute information
struct FieldRecord {
    var Flags: UInt32
    var MangledTypeName: UInt32
    var FieldName: UInt32
}
Copy the code

Get the attributes of a Mach-O file through a pointer

The corresponding operation here is to read the Mach-O file. In the process of writing, you can print the information against the Mach-O to verify whether the reading is correct

Get the correct memory address of __swift5_types
var size: UInt = 0
// __swift5_types
let types_ptr = getsectdata("__TEXT"."__swift5_types".&size)
print(types_ptr)

// Get the __LINKEDIT information from the Mach-o file
var segment_command_linkedit = getsegbyname("__LINKEDIT")

// Get the file memory address of the segment
let vmaddr = segment_command_linkedit?.pointee.vmaddr

// Get the file offset for this segment
let fileoff = segment_command_linkedit?.pointee.fileoff

// Calculate the base address of the link (i.e. the base address of the virtual memory)
var link_base_address = (vmaddr ?? 0) - (fileoff ?? 0)

// The memory address of __swift5_types is added to the base address of virtual memory.
// So to get the correct memory address for Swift, subtract the virtual memory base address from the memory address of __swift5_types
var offset: UInt64 = 0
if let unwrapped_ptr = types_ptr {
    // Convert types_ptr to an integer type to evaluate
    let types_int_representation = UInt64(bitPattern: Int64(Int(bitPattern: unwrapped_ptr)))
    offset = types_int_representation - link_base_address
    print("offset: ", offset)
}

// 2. Get the four bytes of __swift5_types
// Get the base address where the current program is running
var app_base_address = _dyld_get_image_header(0)
print(app_base_address)

// Convert app_base_address to an integer for calculation
let app_base_address_int_representation = UInt64(bitPattern: Int64(Int(bitPattern: app_base_address)))

// Calculate the address of the four bytes of __swift5_types stored in program memory
var data_load_address = app_base_address_int_representation + offset

// We need to get what these four bytes point to
// Convert data_load_address to a pointer type
let data_load_address_ptr = withUnsafePointer(to: data_load_address) { $0 }
let data_load_content = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: data_load_address) ?? 0)?.pointee
print("data_load_content: ",data_load_content)

// 3. Obtain the address of Description
// Get the offset of Description in the mach-o file
let description_offset = offset + UInt64(data_load_content ?? 0) - link_base_address
print("description_offset: ", description_offset)

// Get a pointer to Description in memory
let description_address = description_offset + app_base_address_int_representation

// Point the pointer address of Description to TargetClassDescriptor
let class_description = UnsafePointer<TargetClassDescriptor>.init(bitPattern: Int(exactly: description_address) ?? 0)?.pointee
print("class_description: ", class_description)

// 4. Get attribute information - FieldDescriptor
// 16 is the size of the first four member variables of the FieldDescriptor structure. There are four UInt32, so 16
let fieldDescriptor_address_int_representation = description_offset + 16 + app_base_address_int_representation
print("fieldDescriptor_address_int_representation: ", fieldDescriptor_address_int_representation)

// Convert the fieldDescriptor_address_int_representation to the pointer address, where the value of the address is the offset of the fieldDescriptor
let fieldDescriptorOffset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: fieldDescriptor_address_int_representation) ?? 0)?.pointee
print("fieldDescriptorOffset: ", fieldDescriptorOffset)

// The real memory address of the fieldDescriptor = fieldDescriptor_address_int_representation + offset information
let fieldDescriptorAddress = fieldDescriptor_address_int_representation + UInt64(fieldDescriptorOffset!)
print("fieldDescriptorAddress: ", fieldDescriptorAddress)

// Convert memory address to fieldDescriptor
let fieldDescriptor = UnsafePointer<FieldDescriptor>.init(bitPattern: Int(exactly: fieldDescriptorAddress) ?? 0)?.pointee
print("fieldDescriptor: ", fieldDescriptor)

// Iterate over the property information
for i in 0..<fieldDescriptor!.NumFields {
    let a = MemoryLayout<FieldRecord>.stride
    let stride: UInt64 = UInt64(i * UInt32(a))
    let fieldRecordAddress = fieldDescriptorAddress + 16 + stride
    print(fieldRecordRelactiveAddress)
    // Bind the pointer to the FieldRecord structure
    //let fieldRecord = UnsafePointer
      
       .init(bitPattern: Int(exactly: fieldRecordAddress) ?? 0)? .pointee
      
    //print(fieldRecord)
    
    let fieldNameRelactiveAddress = (fieldRecordAddress + 8 - link_base_address) + app_base_address_int_representation
    let offset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: fieldNameRelactiveAddress) ?? 0)?.pointee
    //print(offset)
    
    // Get the real memory address of attribute name
    let fieldNameAddress = fieldNameRelactiveAddress + UInt64(offset!) - link_base_address
    
    // Convert to a string to display
    if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(fieldNameAddress)){
        print(String(cString: cChar))
    }
}
Copy the code

Write this down to get the class name and attribute name.

Reference article: juejin.cn/post/705421…