Как же все таки изменить байты строки в 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
mod time: 1746050393