问题描述
我在 Swift 中实现了一个密码生成器脚本,它利用 Process()
来执行 Mac OS X 命令行任务。密码本身只是随机字符串,然后由命令行任务加密(bcrypt),如下所示:
/usr/sbin/htpasswd -bnBC 10 '' this_is_the_password | /usr/bin/tr -d ':\n'
还使用多个线程并行生成密码及其哈希值。
注意:多线程和命令行任务(与我尝试过的其他几个本机 Swift 库相比)都提高了性能执行时间大大缩短。
问题
程序在前 ~3148 轮运行良好,并且总是在这个数字附近崩溃(可能与运行的线程数相关)。 例如,如果我配置 2000 个密码,则代码按预期执行会终止而没有任何错误。
常见错误信息
在 Process+Pipe.swift
函数的 catch
块中的 execute(...)
中设置断点会导致:
BREAKPOINT_1
Thread 1: signal SIGCHLD
取消注释四个 po error.localizedDescription
"The operation Couldn\\U2019t be completed. (NSPOSIXErrorDomain error 9 - Bad file descriptor)"
代码片段以忽略错误时,以下错误最终使执行崩溃(再次在 //return self.hash(string,cost: cost)
中,但不一定在 execute(...)
块中):
catch
Program stops ...
Thread 32: EXC_BAD_ACCESS (code=2,address=0x700003e6bfd4)
... on manual continue ...
Thread 2: EXC_BAD_ACCESS (code=2,address=0x700007e85fd4)
代码
相关代码组件是 po process
error: Trying to put the stack in unreadable memory at: 0x700003e6bf40.
,它初始化并启动(然后停止)Main.swift
,然后循环 n 次以通过 PasswordGenerator
获取密码来自nextPassword()
。 PasswordGenerator
本身利用 PasswordGenerator
扩展中的 execute(...)
来运行生成哈希的命令行任务。
Main.swift
Process
PasswordGenerator.swift
class Main {
private static func generate(...) {
...
PasswordGenerator.start()
for _ in 0..<n {
let nextPassword = PasswordGenerator.nextPassword()
let readablePassword = nextPassword.readable
let password = nextPassword.hash
...
}
PasswordGenerator.stop()
...
}
}
并行运行多个线程。
PasswordGenerator
尝试获取密码(如果 nextPassword()
数组中有密码)或等待 100 秒。
passwords
加密.swift
struct PasswordGenerator {
typealias Password = (readable: String,hash: String)
private static let semaphore = dispatchSemaphore(value: 1)
private static var active = false
private static var passwords: [Password] = []
static func nextPassword() -> Password {
self.semaphore.wait()
if let password = self.passwords.popLast() {
self.semaphore.signal()
return password
} else {
self.semaphore.signal()
sleep(100)
return self.nextPassword()
}
}
static func start(
numberOfWorkers: UInt = 32,passwordLength: UInt = 10,cost: UInt = 10
) {
self.active = true
for id in 0..<numberOfWorkers {
self.runWorker(id: id,passwordLength: passwordLength,cost: cost)
}
}
static func stop() {
self.semaphore.wait()
self.active = false
self.semaphore.signal()
}
private static func runWorker(
id: UInt,cost: UInt = 10
) {
dispatchQueue.global().async {
var active = true
repeat {
// Update active.
self.semaphore.wait()
active = self.active
print("numberOfPasswords: \(self.passwords.count)")
self.semaphore.signal()
// Generate Password.
// Important: The bycrypt(cost: ...) step must be done outside the Semaphore!
let readable = String.random(length: Int(passwordLength))
let password = Password(readable: readable,hash: Encryption.hash(readable,cost: cost))
// Add Password.
self.semaphore.wait()
self.passwords.append(password)
self.semaphore.signal()
} while active
}
}
}
Process+Pipe.swift
struct Encryption {
static func hash(_ string: String,cost: UInt = 10) -> String {
// /usr/sbin/htpasswd -bnBC 10 '' this_is_the_password | /usr/bin/tr -d ':\n'
let command = "/usr/sbin/htpasswd"
let arguments: [String] = "-bnBC \(cost) '' \(string)".split(separator: " ").map(String.init)
let result1 = Process.execute(
command: command,//"/usr/sbin/htpasswd",arguments: arguments//["-bnBC","\(cost)","''",string]
)
let errorString1 = String(
data: result1?.error?.fileHandleForReading.readDataToEndOfFile() ?? Data(),encoding: String.Encoding.utf8
) ?? ""
guard errorString1.isEmpty else {
// return self.hash(string,cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) Failed with error: \(errorString1)")
}
guard let output1 = result1?.output else {
// return self.hash(string,cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) Failed! No output.")
}
let command2 = "/usr/bin/tr"
let arguments2: [String] = "-d ':\n'".split(separator: " ").map(String.init)
let result2 = Process.execute(
command: command2,arguments: arguments2,standardInput: output1
)
let errorString2 = String(
data: result2?.error?.fileHandleForReading.readDataToEndOfFile() ?? Data(),encoding: String.Encoding.utf8
) ?? ""
guard errorString2.isEmpty else {
// return self.hash(string,cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) Failed with error: \(errorString2)")
}
guard let output2 = result2?.output else {
// return self.hash(string,cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) Failed! No output.")
}
guard
let hash = String(
data: output2.fileHandleForReading.readDataToEndOfFile(),encoding: String.Encoding.utf8
)?.replacingOccurrences(of: "$2y$",with: "$2a$")
else {
fatalError("Hash: String replacement Failed!")
}
return hash
}
}
问题
- 为什么程序会崩溃?
- 为什么对于 2000 个密码这样的大数字也不会崩溃?
- 多线程实现是否正确?
-
extension Process { static func execute( command: String,arguments: [String] = [],standardInput: Any? = nil ) -> (output: Pipe?,error: Pipe?)? { let process = Process() process.executableuRL = URL(fileURLWithPath: command) process.arguments = arguments let outputPipe = Pipe() let errorPipe = Pipe() process.standardOutput = outputPipe process.standardError = errorPipe if let standardInput = standardInput { process.standardInput = standardInput } do { try process.run() } catch { print(error.localizedDescription) // BREAKPOINT_1 return nil } process.waitUntilExit() return (output: outputPipe,error: errorPipe) } }
代码有问题吗?
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)