mysql_stmt_free_result对成功执行的查询进行了段错误

时间:2017-05-20 17:03:39

标签: mysql c segmentation-fault

我正在使用MySQL C API与我的数据库进行通信。我目前正在迁移到使用预准备语句。但是mysql_stmt_free_result导致了段错误,我无法弄清楚原因。

#define SENSOR_SQL_GET_KEY "SELECT `password` FROM `station` "  \
    "WHERE `station_id`=?;"
int32_t sensor_get_station_key(struct Sensor_Handle *handle, char *key, uint32_t size,
                               uint32_t station_id)
{
    MYSQL_BIND bind;
    uint32_t length;
    MYSQL_STMT *stmt = handle->stmt_get_key;

    memset(&bind,0,sizeof(bind));
    bind.buffer_type=MYSQL_TYPE_LONG;
    bind.buffer=&station_id;
    bind.is_unsigned = 1;

    check(mysql_stmt_bind_param(stmt, &bind)==0, "mysql_stmt_bind_param failed: %s",
          mysql_stmt_error(stmt));
    check(mysql_stmt_execute(stmt)==0, "mysql_stmt_execute failed: %s",
          mysql_stmt_error(stmt));

    memset(&bind,0,sizeof(bind));
    bind.buffer_type=MYSQL_TYPE_STRING;
    bind.buffer=key;
    bind.buffer_length=size;
    bind.length=(unsigned long*)&length;

    check(mysql_stmt_bind_result(stmt, &bind)==0, "mysql_stmt_bind_result failed");

    check(mysql_stmt_store_result(stmt)==0, "mysql_stmt_store_result failed: %s",
          mysql_stmt_error(stmt));

    check(mysql_stmt_num_rows(stmt)==1,
          "mysql_stmt_num_rows didn't return 1, for station_id %i", station_id);

    check(mysql_stmt_fetch(stmt)==0,
          "mysql_stmt_fetch_result failed: %s", mysql_stmt_error(stmt));

    mysql_stmt_free_result(stmt); //segfaults

    return length;

error:
    mysql_stmt_free_result(handle->stmt_get_key);

    return -1;
}

我已经标记了导致段错误的行(使用gdb backtrace获得)。如果我评论该行,一切正常,key(在调用函数中声明的堆栈上的32字节数组)填充了数据库中正确的32byte字符串。所以查询确实成功执行。

另一方面,下面的代码mysql_stmt_free_result执行时不会导致段错误,这会让我感到困惑,因为代码的结构非常相似。

#define SENSOR_SQL_CHECK_SENSOR_STATION "SELECT `station_id` FROM `sensor` " \
    "WHERE sensor_id = ?"
int32_t sensor_check_sensor_station(struct Sensor_Handle *handle, uint32_t sensor_id, uint32_t station_id)
{    
    MYSQL_BIND bind;
    uint32_t id;
    MYSQL_STMT *stmt = handle->stmt_check_sensor_station;

    memset(&bind,0,sizeof(bind));
    bind.buffer_type=MYSQL_TYPE_LONG;
    bind.buffer=&sensor_id;
    bind.is_unsigned = 1;

    check(mysql_stmt_bind_param(stmt, &bind)==0,
          "mysql_stmt_bind_param failed: %s", mysql_stmt_error(stmt));
    check(mysql_stmt_execute(stmt)==0,
          "mysql_stmt_execute failed: %s", mysql_stmt_error(stmt));

    check(mysql_stmt_store_result(stmt)==0,
          "mysql_stmt_store_result failed: %s", mysql_stmt_error(stmt));

    if(!mysql_stmt_num_rows(stmt)) //sensor_id doesn't exist
    {
        mysql_stmt_free_result(stmt);
        return 0;
    }

    memset(&bind,0,sizeof(bind));
    bind.buffer_type=MYSQL_TYPE_LONG;
    bind.buffer=&id;
    bind.is_unsigned=1;

    check(mysql_stmt_bind_result(stmt, &bind)==0, "mysql_stmt_bind_result failed");

    check(mysql_stmt_fetch(stmt)==0,
          "mysql_stmt_fetch_result failed: %s", mysql_stmt_error(stmt));    

    mysql_stmt_free_result(stmt); //no problem

    if(id == station_id)
        return 1;
    else
        return 0;

error:
    return -1;
}

我还在没有报告任何内存访问错误的代码上使用了valgrind memcheck。我已经将定义包含在用于在单独函数中准备语句的实际查询中。

更多信息:

我将准备好的陈述存储在

struct Sensor_Handle
{
    MYSQL *connection;

    MYSQL_STMT *stmt_get_key;
    MYSQL_STMT *stmt_check_sensor_station;
    etc.
};

我准备这样的

int32_t sensor_create_statement(struct Sensor_Handle *handle, MYSQL_STMT **stmt_out, char *sql)
{
    MYSQL_STMT *stmt;

    stmt = mysql_stmt_init(handle->connection);
    check(stmt, "mysql_stmt_init failed: Out of Memory");

    check(mysql_stmt_prepare(stmt, sql, strlen(sql))==0,
          "mysql_stmt_prepare failed: %s", mysql_stmt_error(stmt));
    *stmt_out = stmt;

    return 0;

error:
    return -1;
}

int32_t sensor_statement_init(struct Sensor_Handle *handle)
{
    MYSQL_STMT *stmt;

    //sensor_get_station_key
    check(sensor_create_statement(handle, &stmt, SENSOR_SQL_GET_KEY)==0,
          "sensor_create_statement failed");
    handle->stmt_get_key = stmt;

    //sensor_check_sensor_station
    check(sensor_create_statement(handle, &stmt, SENSOR_SQL_CHECK_SENSOR_STATION)==0,
          "sensor_create_statement failed");
    handle->stmt_check_sensor_station = stmt;

    return 0;

error:
    return -1;
}

在另一个函数中调用segfaults的函数:

char key[AES_KEY_SIZE];

i=sensor_get_station_key(handle, key, sizeof(key), station_id);
check(i==AES_KEY_SIZE,
      "sensor_get_station_id didn't return the expected keysize. Got: %i, expected: %i",
      i, AES_KEY_SIZE);

因此,如果我删除mysql_stmt_free_result

,您甚至可以看到它返回预期的密钥大小

1 个答案:

答案 0 :(得分:1)

    <view
        android:layout_width="match_parent"
        android:layout_height="20dp"/>

这不安全,bind.length被声明为无符号长指针,但是你传递的是32位uint32_t的地址。无符号长可以是64位,如果你在mysql lib写入时最终导致堆栈损坏。