hikan.ru/posts/unsafe-change-string-go.md

72 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Как же все таки изменить байты строки в Go?
Просто захотелось чуть чуть поиграться с пакетом unsafe в Go.
Строки (тип string) в Go являются *immutable*, то есть изменять их нельзя. Ну вообще конечно можно, но не напрямую.
Строка в Go под капотом является структурой вида: **указатель на данные, длина данных.** И первое, что приходит в голову чтобы изменить строку - добраться до поля с указателем, прибавить к нему индекс байта который надо поменять, разыменовать полученный адрес и что то ему присвоить.
Но в реальности все не так просто и при попытке что то положить по вычисленному адресу программа упадет с *segmentation fault (SIGSEGV)*. Чтобы этого избежать, предварительно надо выдать права на запись в страничку памяти где находится целевая строка. Сделать это можно через системные вызовы.
```
package main
import (
"fmt"
"syscall"
"unsafe"
)
func main() {
s := "hello"
fmt.Println("Original:", s)
// Строки в Go - структуры с двумя полями: указатель на данные и длина
// тут строка интерпретируется как структура
strHeader := (*struct {
data unsafe.Pointer
len int
})(unsafe.Pointer(&s))
// Вычисляем начало страницы памяти
pageSize := syscall.Getpagesize()
pageStart := uintptr(strHeader.data) - (uintptr(strHeader.data) % uintptr(pageSize))
fmt.Printf(
"\nАдрес строки = %d\nразмер страницы памяти = %d\nадрес строки относительно начала страницы = %d\nадрес страницы = %d\n\n",
uintptr(strHeader.data),
uintptr(pageSize),
(uintptr(strHeader.data) % uintptr(pageSize)),
pageStart)
// Имея адрес страницы и ее размер даю права на запись на данной странице памяти
ret, _, err := syscall.Syscall(
syscall.SYS_MPROTECT,
pageStart,
uintptr(pageSize),
uintptr(syscall.PROT_READ|syscall.PROT_WRITE),
)
if ret != 0 {
panic("mprotect failed: " + err.Error())
}
// Теперь можно менять строку
// поменяю, например, пятый байт
fifthBytePtr := (*byte)(unsafe.Pointer(uintptr(strHeader.data) + 4))
*fifthBytePtr = 0
// Восстанавливаю дефолтные права
ret, _, err = syscall.Syscall(
syscall.SYS_MPROTECT,
pageStart,
uintptr(pageSize),
uintptr(syscall.PROT_READ),
)
if ret != 0 {
panic("mprotect restore failed: " + err.Error())
}
fmt.Println("Modified:", s) // Должно быть "hell"
}
```
Тестил на **go version go1.22.2 linux/amd64**