swift 如何在MacOS上解释调用proc_listpids函数的结果?

dba5bblo  于 2023-06-21  发布在  Swift
关注(0)|答案(1)|浏览(96)

我正在尝试获取MacOS上的pid列表。librpoc.h 包含此函数签名,但我找不到任何文档:

int proc_listpids(uint32_t type, uint32_t typeinfo, void *buffer, int buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

我在Swift中尝试了以下示例代码:

let arr = UnsafeMutableRawPointer.allocate(byteCount: 5000 * MemoryLayout<Int32>.stride, alignment: MemoryLayout<Int32>.alignment)
let count = proc_listpids(UInt32(PROC_ALL_PIDS), 0, arr, Int32(5000 * MemoryLayout<Int32>.stride));
print("count \(count)")

let pointer = arr.assumingMemoryBound(to: Int32.self)
let pids = Array(UnsafeMutableBufferPointer(start:pointer, count:Int(count)))
let slice = pids[0..<Int(count)]
for i in 0..<Int(count) {
     print("index:\(i) pid:\(slice[i])")
}

最初我认为count是找到的pid的数量。但是输出显示了更多的元素:

count 2020
index:0 pid:7727
index:1 pid:7726
index:2 pid:7723
index:3 pid:7722
index:4 pid:7721
index:5 pid:7717
...
index:502 pid:94
index:503 pid:1
index:504 pid:0
index:505 pid:0
index:506 pid:0
...
index:2013 pid:0
index:2014 pid:0
index:2015 pid:0
index:2016 pid:0
index:2017 pid:0
index:2018 pid:0
index:2019 pid:0

因此,如何解释proc_listpids实际返回的数字是令人困惑的?

oalqel3c

oalqel3c1#

问题

proc_listpids(和许多其他libproc.h API)返回结果将具有的总缓冲区大小 * 以字节为单位 *。所以结果20202020字节,这正好足够存储505Int32 s(当然,每个4字节),这意味着最后一个有效索引是504

为什么返回size而不是count?

这只是我的推测,但我怀疑这是因为它使API便于在C中使用,在C中,此结果可能会直接传递给malloc
如果它们返回的是计数而不是字节数,则调用方必须自己进行转换。由于许多libproc API可以返回几种不同结构中的一种,因此使用错误结构的sizeof(对于您标记的特定类型的请求)可能会导致缓冲区太小(存在缓冲区溢出漏洞的风险)或太大(存在缓冲区下溢漏洞的风险)。

工作示例

下面的代码片段显示了对proc_listpids的工作调用:

func getAllPIDs1() -> [pid_t] {
    let expectedSize = proc_listpids(UInt32(PROC_ALL_PIDS), 0, nil, 0)

    guard expectedSize != -1 else {
        fatalError("Something went wrong... but what exactly?")
    }

    let stride = Int32(MemoryLayout<pid_t>.stride)
    let expectedCount = Int(expectedSize / stride)

    return Array(unsafeUninitializedCapacity: expectedCount) { buffer, initializedCount in
        let initializedSize = proc_listpids(UInt32(PROC_ALL_PIDS), 0, &buffer, Int32(initializedCount))
        initializedCount = Int(initializedSize / stride)
    }
}

如果使用proc_listallpids,事情会变得简单一些,这只是这种情况下的一个方便 Package 器(源代码):

func getAllPIDs2() -> [pid_t] {
    let exptectedCount = proc_listallpids(nil, 0)

    guard exptectedCount != -1 else {
        fatalError("Something went wrong... but what exactly?")
    }

    let expectedSize = Int(exptectedCount) * MemoryLayout<pid_t>.stride

    return Array(unsafeUninitializedCapacity: expectedSize) { buffer, initializedCount in
        initializedCount = Int(proc_listallpids(&buffer, Int32(initializedCount)))
    }
}

一些注意事项:

  • 这两种方法都可以“正确地调整”数组的大小,这听起来很理想,但也有一个问题
  • 如果一个新进程在第一次和第二次调用proc_listpids之间启动,那么缓冲区将太小而无法容纳它。
  • 这两个调用通常发生在非常快速的连续(所以这是不太可能的),如果您的进程在调用之间被抢占和挂起,则可能会更长,从而导致更多的变化发生。
  • 也许最好使用proc_listpids的结果作为对大小的初始猜测,然后添加一些额外的值以考虑可能出现的任何新进程。
  • 您可以使用Array.init(unsafeUninitializedCapacity:initializingWith:)为Swift Array分配缓冲区,并直接初始化它,而无需任何中间分配或复制。
  • 注意,这个API基本上就像malloc,误用会导致巨大的问题。

额外

如果你发现自己调用了许多不同的libproc API,我发现定义一个helper来抽象掉这段舞蹈的一部分(比如这些计数计算、整数转换、Assert等)是很有用的:

private func extractIntoArray<T>(
    of _: T.Type,
    expectedSize: Int32,
    using body: (UnsafeMutablePointer<T>) throws -> Int32
) rethrows -> [T] {
    let stride = Int32(MemoryLayout<T>.stride)
    // Sanity check: this can catch a lot of cases where you provide the wrong
    // expected structure for the particular "type" of call you're making.
    assert(expectedSize.isMultiple(of: stride))
    let expectedCount = Int(expectedSize / stride)

    return try Array(unsafeUninitializedCapacity: expectedCount) { buffer, initializedCount in
        let initializedSize = try body(buffer.baseAddress!)
        assert(initializedSize.isMultiple(of: stride))
        initializedCount = Int(initializedSize / stride)
    }
}

struct LibProcError: Error {}

func getAllPIDs() throws -> [pid_t] {
    let bufferSize = proc_listpids(UInt32(PROC_ALL_PIDS), 0, nil, 0)

    guard bufferSize != -1 else {
        throw LibProcError()
    }
    
    return try extractIntoArray(of: pid_t.self, expectedSize: bufferSize, using: { bufferP in
        proc_listpids(UInt32(PROC_ALL_PIDS), 0, &buffer, Int32(initializedCount))
    })
}

相关问题