2017-02-12 4 views
2

В C Ниже код сниффера,Тип Safe Vs затягивая напечатал - GO Vs C

void Handle_IP(char *buf) 
{ 
    struct iphdr *ip_hdr; // declaring pointer of type ip header 

    struct in_addr in; // declaring a structure which holds ip address 

    FILE *fp; 
    int ctl, len; 

    /* In the following statement we're adjusting the offset so that 
     ip pointer can point to correct location */ 

    ip_hdr = (struct iphdr *)(buf + 14); 
    .... 
} 

, где линия,

ip_hdr = (struct iphdr *)(buf + 14) имеет char *, который типа отлиты в struct iphdr *.

GO, являющийся безопасным языком типа, не допускает такого рода литье. Go не имеет void *, за исключением interface, который следует за структурной типизацией

Как подойти к такому коду в GO?

+0

@soundslikeodd Вы можете отредактировать еще раз? Извините – overexchange

+0

на самом деле это может быть небезопасно в C ... –

+0

Код C делает разные вещи на разных компиляторах и машинах из-за различий в ограничениях выравнивания, выравнивания, отступов и контентов. Это может даже вызвать segfault. Безопасная, переносная вещь, которую нужно сделать, - это извлечь поле за раз, в противоположность тому, что [этот ответ] (http://stackoverflow.com/a/39132179/589924). – ikegami

ответ

3

Предисловие: Несмотря на то, что вы хотите, старайтесь избегать использования пакета unsafe в максимально возможной степени. Это не решение «идти вперёд», а скорее рассматривать его как последнее средство (или что-то, что приходит после этого).


Go действительно дает вам поддержку для этого в пакете unsafe.

Даже спецификация имеет выделенный раздел для этого. Spec: Package unsafe:

Встроенный пакет unsafe, известный компилятор, предоставляет средства для низкоуровневого программирования, включая операции, которые нарушают систему типов. Пакет с использованием unsafe должен быть проверен вручную для безопасности типа и не может быть переносимым.

Пакет был намеренно назван unsafe, что дает вам правильное предварительное предупреждение о том,

Что вам нужно, это тип unsafe.Pointer. Pointer является специальным pointer type, который может быть не dereferenced, но любой тип указателя может быть преобразован в Pointer, а Pointer может быть преобразован в любой (другой) тип указателя. Так что это ваш «шлюз» между разными типами.

Например, если у вас есть значение типа float64 (который составляет 8 байт в памяти), можно интерпретировать эти 8 байт в виде int64 (который также 8 байт в памяти), как это:

var f float64 = 1 
var i int64 

ip := (*int64)(unsafe.Pointer(&f)) 
i = *ip 

fmt.Println(i) 

Output (попробуйте на Go Playground):

4607182418800017408 

ключ эта строка:

(*int64)(unsafe.Pointer(&f)) 

Это значит взять адрес f (который будет типа *float64), преобразовать его в unsafe.Pointer (любой тип указателя может быть преобразован в Pointer), а затем преобразовать эту unsafe.Pointer значения другого типа указателя *int64. Если мы разыграем этот указатель, получим значение типа int64.

В вашем примере вы хотите «поместить» переменную на адрес с примененным смещением. Go не имеет арифметики указателя. Вы можете обойти это 2-мя способами:

  1. использование uintptr, который может содержать адрес, но вы можете рассматривать его как int и добавить значения к нему

  2. или использовать указатель на «буфер типа, например *[1<<31]byte; вы можете преобразовать адрес в этот указатель, и вы можете применить смещение путем индексации буфера с заданным смещением и взять адрес этого элемента, например. Метод &buf[14]

# 1 может выглядеть это нравится:

type X [16]byte 
var x = X{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 

xx := (uintptr)(unsafe.Pointer(&x[0])) + 14 // APPLY OFFSET 

var x2 X = *(*X)(unsafe.Pointer(xx)) 
fmt.Println(x2[0], x2[1]) 

Output (попробовать его на Go Playground):

14 15 

Метод # 2 может выглядеть следующим образом:

type X [16]byte 
var x = X{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 

xx := (*X)(unsafe.Pointer(&x[0])) 
xx = (*X)(unsafe.Pointer(&xx[14])) // APPLY OFFSET 

var x2 X = *(*X)(unsafe.Pointer(xx)) 
fmt.Println(x2[0], x2[1]) 

Результат такой же. Попробуйте на Go Playground.

6

Давайте рассмотрим это под другим углом: что делает приведение типов в C?

Ну, в некотором смысле, это ничего не делает! В какой-то момент времени в памяти требуется немного битового шаблона, и слепо предполагает, что этот конкретный битовый шаблон фактически представляет значение некоторого типа. Но нет никакой проверки этого, никакого разбора. И в этом проблема.

Но я уже использовал слово, которое является решением нашей проблемы выше: синтаксический анализ. Вместо того, чтобы просто слепо предположить, что некоторый случайный бит-шаблон в памяти является одним и тем же битовым шаблоном, который компилятор C генерирует для значения какого-либо типа, мы на самом деле посмотрим на шаблон бита, проверьте, соответствует ли он правилам того, как бит шаблоны для этого типа данных, и деконструируют битовые шаблоны в свои составные части в соответствии с этими правилами.

Иными словами: мы пишем парсер.

Обратите внимание, что это не относится к Go. Это хорошая идея, даже в С. В самом деле, код размещен сломана, по крайней мере, три, может быть четыре различных способа:

  • платформ, где char не 8 бит шириной
  • выравнивание
  • обивка
  • порядок байт

AFAIK, там уже существующих IP, UDP и TCP парсеры, написанные на Go там, что вы можете использовать или учиться. Кроме того, существующая система из Исследовательского института взглядов Алана Кэй имеет действительно классный стек TCP/IP, написанный всего за 120 строк или около того, что очень легко читать. (На самом деле исходный код анализатора фактически состоит только из диаграмм ASCII, вырезанных &, вставленных из RFC.)

Смежные вопросы